I am new to Netty and use version 4. In my project, a server returns client a Java object, which can be large. I started by using ObjectEncoder/Decoder and NioSocketChannel for this purpose. Though it works, the performance is significantly worse than it is with old blocking IO. Thread dumps show that ObjectEncoder reallocates direct buffers all the time. My guess is it is serializing the whole object in the direct buffer and only then sends it over the network. This is slow and may cause OutOfMemoryError if there are multiple requests like this running simultaneously. What is your suggestion for an efficient implementation, which would be fast and use a limited size buffer? Also, some (but not all) of the objects, which the server returns, contain a long byte array field. Can this fact be used to further improve the performance?
As @MattBakaitis asked, I am pasting the code sample, which is a slight modification of the ObjectEchoServer example. It sends a constant large object back to the client in response to a message received.
public final class MyObjectEchoServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "11000"));
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(
new ObjectEncoder(),
new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)),
new ObjectEchoServerHandler());
}
});
// Bind and start to accept incoming connections.
b.bind(PORT).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class ObjectEchoServerHandler extends ChannelInboundHandlerAdapter {
public static class Response implements Serializable {
public byte[] bytes;
}
private static Response response;
static {
int len = 256 * 1024 * 1024;
response = new Response();
response.bytes = new byte[len];
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("Received: msg=" + msg);
// Echo back the received object to the client.
System.out.println("Sending response. length: " + response.bytes.length);
ctx.write(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("Flushing");
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
It works with no errors if JVM has enough memory, however it is slow and throws a Direct buffer OutOfMemeoryError if multiple clients are running or the response object is too large. I did multiple thread dumps and they always like the one I pasted below and show that ObjectEncoder writes a response object in a direct buffer and constantly resizes this buffer as the response is large. Therefore, I think that this kind of straight forward implementation is not efficient and looking for an advice what would be the efficient approach.
Thread stack I mentioned:
"nioEventLoopGroup-3-1" prio=10 tid=0x000000000bf88800 nid=0x205c runnable [0x000000000cb5e000]
java.lang.Thread.State: RUNNABLE
at sun.misc.Unsafe.copyMemory(Native Method)
at sun.misc.Unsafe.copyMemory(Unsafe.java:560)
at java.nio.DirectByteBuffer.put(DirectByteBuffer.java:326)
at io.netty.buffer.UnpooledUnsafeDirectByteBuf.capacity(UnpooledUnsafeDirectByteBuf.java:160)
at io.netty.buffer.AbstractByteBuf.ensureWritable(AbstractByteBuf.java:251)
at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:818)
at io.netty.buffer.ByteBufOutputStream.write(ByteBufOutputStream.java:66)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(ObjectOutputStream.java:1876)
at java.io.ObjectOutputStream$BlockDataOutputStream.write(ObjectOutputStream.java:1847)
at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1333)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1173)
at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1547)
at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1508)
at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1431)
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1177)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:347)
at io.netty.handler.codec.serialization.ObjectEncoder.encode(ObjectEncoder.java:47)
at io.netty.handler.codec.serialization.ObjectEncoder.encode(ObjectEncoder.java:36)
at io.netty.handler.codec.MessageToByteEncoder.write(MessageToByteEncoder.java:111)
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:657)
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:715)
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:650)
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:636)
at io.netty.example.objectecho.ObjectEchoServerHandler.channelRead(ObjectEchoServerHandler.java:46)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:163)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:332)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:787)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:507)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:464)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:378)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:350)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:116)
at java.lang.Thread.run(Thread.java:745)