1. ByteBuf
2. 問題描述
日志記錄中報堆外內存溢出。
3. 問題定位及修改
Netty提供了ByteBuf泄露的檢測機制。
JVM啟動參數中添加: -Dio.netty.leakDetectionLevel=advanced , log4j2.xml配置io.netty日志記錄即可。
禁用(DISABLED) – 完全禁止泄露檢測,省點消耗。
簡單(SIMPLE) – 默認等級,告訴我們取樣的1%的ByteBuf是否發生了泄露,但總共一次只打印一次,看不到就沒有了。 高級(ADVANCED) – 告訴我們取樣的1%的ByteBuf發生泄露的地方。每種類型的泄漏(創建的地方與訪問路徑一致)只打印一次。對性能有影響。 偏執(PARANOID) – 跟高級選項類似,但此選項檢測所有ByteBuf,而不僅僅是取樣的那1%。對性能有絕大的影響。
檢測到如下泄露點,
舉例1:
13:29:25.273 [MODBUS_MESSAGE_POOL-thread-14] [] [] [] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
Created at:
io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:604)
io.netty.buffer.AbstractByteBuf.readBytes(AbstractByteBuf.java:849)
com.pinnet.protocol.hwmodbus.message.ModbusMessageRunnable.run(ModbusMessageRunnable.java:46)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
java.lang.Thread.run(Thread.java:748)
可以看到readBytes()創建后,沒有釋放。
修改:
buffer.readBytes(4).release();
舉例2:
2018-06-30 13:29:25.278 [MESSAGE_POOL-thread-14] [] [] [] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
#1:
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:273) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1380) io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1159) io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1194) io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) #2: io.netty.buffer.AdvancedLeakAwareByteBuf.getUnsignedShort(AdvancedLeakAwareByteBuf.java:172) io.netty.handler.codec.LengthFieldBasedFrameDecoder.getUnadjustedFrameLength(LengthFieldBasedFrameDecoder.java:469) io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:417) com.pinnet.protocol.hwmodbus.tcp.MobusLengthDecoder.decode(MobusLengthDecoder.java:32) #3: io.netty.buffer.AdvancedLeakAwareByteBuf.order(AdvancedLeakAwareByteBuf.java:70) io.netty.handler.codec.LengthFieldBasedFrameDecoder.getUnadjustedFrameLength(LengthFieldBasedFrameDecoder.java:462) io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:417) com.pinnet.protocol.hwmodbus.tcp.MobusLengthDecoder.decode(MobusLengthDecoder.java:32) io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:343) io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:489) io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:428) io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:265) #4: Hint: 'decoder' will handle the message from this point. #5: Hint: 'idleHandler' will handle the message from this point.
修改:解碼器中釋放Bytebuf in
public class MobusLengthDecoder extends LengthFieldBasedFrameDecoder { public MobusLengthDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) { super(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast); } @Override protected Object decode(ChannelHandlerContext ctx, io.netty.buffer.ByteBuf in) throws Exception { ByteBuf buf = (ByteBuf) super.decode(ctx, in); // LEAK: ByteBuf.release() was not called before it's garbage-collected. in.release(); ... } }
