解決緩衝區泄漏
引用計數的缺點是容易泄漏引用計數的對象。因爲JVM不瞭解Netty實現的引用計數,所以一旦它們無法訪問,即使它們的引用計數不爲零,它也會自動對它們進行GC。一旦回收了垃圾,就無法將其復活,因此不能將其返回到它來自的池中,從而會導致內存泄漏。
幸運的是,儘管難以發現泄漏,但Netty默認情況下會抽樣大約1%的緩衝區分配,以檢查應用程序中是否存在泄漏。如果發生泄漏,您將發現以下日誌消息:
LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=advanced' or call ResourceLeakDetector.setLevel()
使用上面提到的JVM選項重新啓動您的應用程序,然後你將看到應用程序的最近位置,其中該位置訪問了泄漏的緩衝區。以下輸出顯示了單元測試(XmlFrameDecoderTest.testDecodeWithXml()
)的泄漏:
Running io.netty.handler.codec.xml.XmlFrameDecoderTest
15:03:36.886 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 1
#1:
io.netty.buffer.AdvancedLeakAwareByteBuf.toString(AdvancedLeakAwareByteBuf.java:697)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:157)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
...
Created at:
io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
io.netty.buffer.UnpooledUnsafeDirectByteBuf.copy(UnpooledUnsafeDirectByteBuf.java:465)
io.netty.buffer.WrappedByteBuf.copy(WrappedByteBuf.java:697)
io.netty.buffer.AdvancedLeakAwareByteBuf.copy(AdvancedLeakAwareByteBuf.java:656)
io.netty.handler.codec.xml.XmlFrameDecoder.extractFrame(XmlFrameDecoder.java:198)
io.netty.handler.codec.xml.XmlFrameDecoder.decode(XmlFrameDecoder.java:174)
io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:227)
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:140)
io.netty.channel.ChannelHandlerInvokerUtil.invokeChannelReadNow(ChannelHandlerInvokerUtil.java:74)
io.netty.channel.embedded.EmbeddedEventLoop.invokeChannelRead(EmbeddedEventLoop.java:142)
io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:317)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
io.netty.channel.embedded.EmbeddedChannel.writeInbound(EmbeddedChannel.java:176)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithXml(XmlFrameDecoderTest.java:147)
io.netty.handler.codec.xml.XmlFrameDecoderTest.testDecodeWithTwoMessages(XmlFrameDecoderTest.java:133)
...
如果您使用Netty 5或更高版本,則將提供其他信息來幫助您找到哪個處理程序最後處理了泄漏的緩衝區。下面的示例顯示泄漏的緩衝區是由名稱爲的處理程序處理的EchoServerHandler#0
,然後進行了垃圾回收,這意味着可能EchoServerHandler#0
忘記了釋放緩衝區:
12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 2
#2:
Hint: 'EchoServerHandler#0' will handle the message from this point.
io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329)
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
java.lang.Thread.run(Thread.java:744)
#1:
io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589)
io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
java.lang.Thread.run(Thread.java:744)
Created at:
io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146)
io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107)
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
java.lang.Thread.run(Thread.java:744)
Leak detection levels
這個有四種級別:
- DISABLED :完全禁用泄漏檢測。不建議。
- SIMPLE:告訴1%的緩衝區是否泄漏。默認。
- ADVANCED:告訴哪裏有1%的緩衝區訪問了泄漏的緩衝區。
- PARANOID :與ADVANCED相同,除了用於每個單個緩衝區。對於自動化測試階段很有用。如果構建輸出包含“
LEAK:
”,則可能使構建失敗。
您可以將泄漏檢測級別指定爲JVM選項 -Dio.netty.leakDetection.level
java -Dio.netty.leakDetection.level=advanced
注意:此屬性以前稱爲io.netty.leakDetectionLevel
。
避免泄漏的最佳做法
- 在
PARANOID
泄漏檢測級別和級別運行單元測試和集成測試SIMPLE
。 - 在
SIMPLE
相當長的一段時間內將其擴展到整個集羣之前,先對應用程序進行Canary處理,以查看是否存在泄漏。 - 如果存在泄漏,請再次在金絲雀
ADVANCED
級別上獲取有關泄漏源的一些提示。 - 不要將存在泄漏的應用程序部署到整個集羣。
修復單元測試中的泄漏
忘記在單元測試中釋放緩衝區或消息非常容易。它將生成泄漏警告,但這並不一定意味着您的應用程序存在泄漏。try-finally
可以使用ReferenceCountUtil.releaseLater()
實用程序方法來代替用塊包裝單元測試以釋放所有緩衝區:
import static io.netty.util.ReferenceCountUtil.*;
@Test
public void testSomething() throws Exception {
// ReferenceCountUtil.releaseLater() will keep the reference of buf,
// and then release it when the test thread is terminated.
ByteBuf buf = releaseLater(Unpooled.directBuffer(512));
...
}