this.messages = new ConcurrentLinkedQueue<>();
to buffer messages before sending. This results in a lot of object allocations as every message also gets a queue node created. It would be useful to consider a bounded alternative like ArrayBlockingQueue with ~million entries (configurable with a property). It looks like offer is already being used instead of add so this would also cap the memory limits of the application.