netty記憶體洩漏,困擾了好幾天的問題找到原文了
自從Netty 4開始,物件的生命週期由它們的引用計數(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用計數來改進分配記憶體和釋放記憶體的效能。
基本的引用計數
每個物件的初始計數為1:
Java程式碼- ByteBuf buf = ctx.alloc().directBuffer();
- assert buf.refCnt() == 1;
當你釋放(release)引用計數物件時,它的引用計數減1.如果引用計數為0,這個引用計數物件會被釋放(deallocate),並返回物件池。
- assert buf.refCnt() == 1;
- // release() returns true only if the reference count becomes 0.
- boolean destroyed = buf.release();
- assert destroyed;
- assert buf.refCnt() == 0;
懸垂(dangling)引用
嘗試訪問引用計數為0的引用計數物件會丟擲IllegalReferenceCountException異常:
Java程式碼- assert buf.refCnt() == 0;
- try
- buf.writeLong(0xdeadbeef);
- thrownew Error("should not reach here");
- } catch (IllegalReferenceCountExeception e) {
- // Expected
- }
增加引用計數
可通過retain()操作來增加引用計數,前提是此引用計數物件未被銷燬:
(譯者注:跟未使用ARC的objective-c好像)
Java程式碼- ByteBuf buf = ctx.alloc().directBuffer();
- assert buf.refCnt() == 1;
- buf.retain();
- assert buf.refCnt() == 2;
- boolean destroyed = buf.release();
- assert !destroyed;
- assert buf.refCnt() == 1;
誰來銷燬(destroy)
通常的經驗法則是誰最後訪問(access)了引用計數物件,誰就負責銷燬(destruction)它。具體來說是以下兩點:
- 如果元件(component)A把一個引用計數物件傳給另一個元件B,那麼元件A通常不需要銷燬物件,而是把決定權交給元件B。
- 如果一個元件不再訪問一個引用計數物件了,那麼這個元件負責銷燬它。
下面是一個簡單的例子:
Java程式碼- public ByteBuf a(ByteBuf input) {
- input.writeByte(42);
- return input;
- }
- public ByteBuf b(ByteBuf input) {
- try {
- output = input.alloc().directBuffer(input.readableBytes() + 1);
- output.writeBytes(input);
- output.writeByte(42);
- return output;
- } finally {
- input.release();
- }
- }
- publicvoid c(ByteBuf input) {
- System.out.println(input);
- input.release();
- }
- publicvoid main() {
- ...
- ByteBuf buf = ...;
- // This will print buf to System.out and destroy it.
- c(b(a(buf)));
- assert buf.refCnt() == 0;
- }
行為(Action) 誰來釋放(Who should release)? 誰釋放了(Who released)?
1. main()建立了buf buf→main()
2. buf由main()傳給了a() buf→a()
3. a()僅僅返回了buf buf→main()
4. buf由main()傳給了b() buf→b()
5. b()返回了buf的拷貝 buf→b(), copy→main() b()釋放了buf
6. 拷貝由main()傳給了c() copy→c()
7. c()消耗(swallow)了拷貝 copy→c() c()釋放了拷貝
子緩衝(Derived buffers)
ByteBuf.duplicate(), ByteBuf.slice()和ByteBuf.order(ByteOrder)建立了子緩衝,這些快取共享了它們的父緩衝(parent buffer)的一部分記憶體。子緩衝沒有自己的引用計數,而是共享父緩衝的引用計數。
Java程式碼- ByteBuf parent = ctx.alloc().directBuffer();
- ByteBuf derived = parent.duplicate();
- // Creating a derived buffer does not increase the reference count.
- assert parent.refCnt() == 1;
- assert derived.refCnt() == 1;
注意父緩衝和它的子緩衝共享同樣的引用計數,當建立子緩衝時並不會增加物件的引用計數。因此,如果你要傳遞(pass)一個子緩衝給你的程式中的其他元件的話,你得先呼叫retain()。
Java程式碼- ByteBuf parent = ctx.alloc().directBuffer(512);
- parent.writeBytes(...);
- try {
- while (parent.isReadable(16)) {
- ByteBuf derived = parent.readSlice(16);
- derived.retain();
- process(derived);
- }
- } finally {
- parent.release();
- }
- ...
- publicvoid process(ByteBuf buf) {
- ...
- buf.release();
- }
ByteBufHolder介面
有時候,一個ByteBuf被一個buffer holder持有,諸如DatagramPacket, HttpContent,和WebSocketframe。它們都擴充套件了一個公共介面,ByteBufHolder。
一個buffer holder共享它所持有的引用計數,如同子緩衝一樣。
ChannelHandler中的引用計數
Inbound訊息(messages)
當一個事件迴圈(event loop)讀入了資料,用讀入的資料建立了ByteBuf,並用這個ByteBuf觸發了一個channelRead()事件時,那麼管道(pipeline)中相應的ChannelHandler就負責釋放這個buffer。因此,處理接收到的資料的handler應該在它的channelRead()中呼叫buffer的release()。
Java程式碼- publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
- ByteBuf buf = (ByteBuf) msg;
- try {
- ...
- } finally {
- buf.release();
- }
- }
如同在本文件中的“誰來銷燬”一節所解釋的那樣,如果你的handler傳遞了快取(或任何引用計數物件)到下一個handler,你就不需要釋放它:
Java程式碼- publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
- ByteBuf buf = (ByteBuf) msg;
- ...
- ctx.fireChannelRead(buf);
- }
注意ByteBuf不是Netty中唯一一種引用計數物件。由解碼器(decoder)生成的訊息(messages)物件,這些物件很可能也是引用計數物件:
Java程式碼- // Assuming your handler is placed next to `HttpRequestDecoder`
- publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
- if (msg instanceof HttpRequest) {
- HttpRequest req = (HttpRequest) msg;
- ...
- }
- if (msg instanceof HttpContent) {
- HttpContent content = (HttpContent) msg;
- try {
- ...
- } finally {
- content.release();
- }
- }
- }
如果你抱有疑問,或者你想簡化這些釋放訊息的工作,你可以使用ReferenceCountUtil.release():
Java程式碼- publicvoid channelRead(ChannelHandlerContext ctx, Object msg) {
- try {
- ...
- } finally {
- ReferenceCountUtil.release(msg);
- }
- }
還有一種選擇,你可以考慮繼承SimpleChannelHandler,它在所有接收訊息的地方都呼叫了ReferenceCountUtil.release(msg)。
Outbound訊息(messages)
與inbound訊息不同,你的程式所建立的訊息物件,由Netty負責釋放,釋放的時機是在這些訊息被髮送到網路之後。但是,在傳送訊息的過程中,如果有handler截獲(intercept)了你的傳送請求,並建立了一些中間物件,則這些handler要確保正確釋放這些中間物件。比如編碼器(encoder)。
Java程式碼- // Simple-pass through
- publicvoid write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
- System.err.println("Writing: " + message);
- ctx.write(message, promise);
- }
- // Transformation
- publicvoid write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
- if (message instanceof HttpContent) {
- // Transform HttpContent to ByteBuf.
- HttpContent content = (HttpContent) message;
- try {
- ByteBuf transformed = ctx.alloc().buffer();
- ....
- ctx.write(transformed, promise);
- } finally {
- content.release();
- }
- } else {
- // Pass non-HttpContent through.
- ctx.write(message, promise);
- }
- }
解決(troubleshooting)buffer洩露
引用計數的缺點是容易發生洩露。因為JVM並不知道Netty實現的引用計數的存在,一旦某些物件不可達(unreachable)就會被自動GC掉,即使這些物件的引用計數不為0。被GC掉的物件就不可用了,因此這些物件也就不能回到物件池中,或者產生記憶體洩露。
幸運的是,儘管要找到洩露很困難,但Netty提供了一種方案來幫助發現洩露,此方案預設在你的程式中的已分配的緩衝中取樣(sample)大約1%的快取,來檢查是否存在洩露。如果存在洩露,你會發現如下日誌:
Plain text程式碼- 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選項(option)重新啟動你的程式,你可以看到在你的程式中最近訪問已洩露的記憶體的位置(location)。下列輸出展示了來自單元測試的一個洩露問題(XmlFrameDecoderTest.testDecodeWithXml()):
Java程式碼- 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或以上的版本,還提供了一個額外的資訊,幫助我們找到最後操作了(handle)洩露緩衝的handler。下面的例子展示了名為EchoServerHandler#0的handler操作了已洩露的緩衝,並且緩衝已被GC了,這意味著EchoServerHandler#0忘記釋放了這個buffer:
Java程式碼- 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)
洩露檢測級別
當前有4個洩露檢測級別:
- 禁用(DISABLED) - 完全禁止洩露檢測。不推薦。
- 簡單(SIMPLE) - 告訴我們取樣的1%的緩衝是否發生了洩露。預設。
- 高階(ADVANCED) - 告訴我們取樣的1%的緩衝發生洩露的地方
- 偏執(PARANOID) - 跟高階選項類似,但此選項檢測所有緩衝,而不僅僅是取樣的那1%。此選項在自動測試階段很有用。如果構建(build)輸出包含了LEAK,可認為構建失敗。
你可以使用JVM的-Dio.netty.leakDetectionLevel選項來指定洩漏檢測級別。
Bash程式碼- java -Dio.netty.leakDetectionLevel=advanced ...
避免洩露的最佳實踐
- 在簡單級別和偏執級別上執行你的單元測試和整合測試(integration tests)。
- 在rolling out到整個叢集之前,使用簡單級別,以一個合理的、足夠長的時間canary(金絲雀?不明所以。。)你的程式,來發現是否存在洩露。
- 如果存在洩露,再用高階級別來canary以獲得一些關於洩露的提示。
- 不要部署存在洩露的程式到整個叢集。
在單元測試中修復洩露問題
在單元測試中很容易忘記釋放緩衝。這會產生一個洩露的警告,但並不是說就肯定存在洩露。你可以使用ReferenceCountUtil.releaseLater()工具方法,放棄用try-finally來包裹你的單元測試程式碼以釋放所有的緩衝:
Java程式碼- importstatic io.netty.util.ReferenceCountUtil.*;
- @Test
- publicvoid 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));
- ...
- }
相關推薦
netty記憶體洩漏,困擾了好幾天的問題找到原文了
自從Netty 4開始,物件的生命週期由它們的引用計數(reference counts)管理,而不是由垃圾收集器(garbage collector)管理了。ByteBuf是最值得注意的,它使用了引用計數來改進分配記憶體和釋放記憶體的效能。 基本的引用計數 每個物件的
對於Spring對websocket的屬性注入失敗問題,困擾我一天,最後終於解決了
首先匯入包必須的: <!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket --> <dependency> <groupId>
記憶體溢位,記憶體洩漏,記憶體抖動
記憶體溢位,記憶體洩漏,記憶體抖動你都碰到過嗎?怎麼解決的?如何區分這幾種情況?怎麼解決由記憶體洩漏而導致的記憶體溢位? 記憶體優化 . 記憶體洩露 記憶體溢位 記憶體抖動 分析與解決 記憶體溢位和記憶體洩漏的區別、產生原因以及解決方案 一、記憶體溢位: (一)、定義: 記憶體溢
淺談記憶體洩漏,野指標,記憶體申請
拿到quiz好難過,記憶體洩漏一個Vector一個Array秀的我頭疼。 記憶體洩漏 百度百科上的定義:記憶體洩漏(Memory Leak)是指程式中己動態分配的堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。 一看定義
關於 記憶體洩漏,野指標;
記憶體洩漏: 動態申請的記憶體空間沒有正常釋放,但也不能繼續使用; char *a = new char('a'); char *b = new char('b'); a = b; ‘a’的空間未被釋放,也無法訪問,形成記憶體洩漏; (基類的解構函
iOS NSString 記憶體洩漏 , 求解決
遞迴, autorelease物件如何避免記憶體洩漏???? -(NSString *) getStuJsonString : (StuNode *) stuNode{ NSString *nodeJsonString = [[NSString alloc] i
Netty記憶體洩漏檢測機制
廣泛使用直接記憶體是Netty成為高效網路框架的原因之一。然而,直接記憶體釋放並不受GC的控制,Netty中的對於直接記憶體的使用類似與C語言中(malloc、free),需要開發者手動分配和回收記憶體,而JVM GC只負責回收JAVA堆上的引用以及堆中記憶體。
記憶體洩漏,記憶體溢位,ANR
Android記憶體洩漏的檢測流程、捕捉以及分析 通常記憶體洩漏的隱藏性質比較強,不同於異常導致的程式Crash,在異常導致的Crash中,我們能夠及時的發現程式問題的存在,並通過log日誌定位到問題所在的具體位置,然後及時進行解決,而記憶體洩漏則不同,在APP中存在記
Netty學習之旅----原始碼分析Netty記憶體洩漏檢測
1、圖說Netty直接記憶體管理 2、Netty 直接記憶體的使用示例 ByteBuf buf = Unpooled.directBuffer(512); System.out.println(buf); // Si
Handler的正確使用,使用靜態內部類+弱引用,解決記憶體洩漏,舉例說明在使用ProgressBar時的Handler類的靜態內部類實現。
在Android中使用ProgressBar進行回撥設定進度時候會提示‘This Handler class should be static or leaks might occur’的警告,那就說明是你的自定義Handler類有記憶體洩露的問題,一般來說就是
Android 中 Activity的記憶體洩漏,原因以及處理方法
文章參考: 八個造成 Android 應用記憶體洩露的原因 Android記憶體洩漏產生的原因以及解決方案OOM android 常見記憶體洩漏原因及解決辦法 記憶體洩漏,說的更直白點,就是你想讓一個物件在下次GC的時候徹底被回收,但是呢,這個物件所處的
Leaks檢查記憶體洩漏,記憶體清理、監控的Android開發工具
MemoryMonitor 一個給開發者使用的Android App記憶體清理、監控工具,可以獲取當前手機的記憶體使用比率,可用記憶體大小,檢查一個APP是否存在記憶體洩漏。並且整理了一些優化記憶體的方式。 1.記憶體清理 類似360衛士的 加速球,獲取系統已用
VS檢測記憶體洩漏,定位洩漏程式碼位置方法
1、什麼是記憶體洩漏? 記憶體洩漏指的是在程式裡動態申請的記憶體在使用完後,沒有進行釋放,導致這部分記憶體沒有被系統回收,久而久之,可能導致程式記憶體不斷增大,系統記憶體不足……引發一系列災難性後果;(關於程式申請記憶體分配方式,詳見:記憶體分配方式) 2、零容忍 排除
tomcat記憶體不足,一段時間後不響應了
tomcat出現記憶體不足,記憶體洩露,記憶體溢位問題: tomcat在使用一段時間後,記憶體不足,然後便不響應了。 解決辦法: 在tomcat的bin的catalina.bat裡的 rem ----- Execute The Requested Com
IOS效能調優系列:使用Instruments動態分析記憶體洩漏,調優instruments
第一篇介紹了Analyze對App做靜態分析,可以發現應用中的記憶體洩漏問題,對於有些記憶體洩漏情況通過靜態分析無法解決的,可以通過動態分析來發現,分析起來更有針對性。 從本篇開始介紹XCode提供的強大的分析工具Instruments,記憶體分析只是Instruments中的一個功能,其他功能後續介紹
JAVACV記憶體洩漏,std::exception: bad allocation
參考了網上諸多opencv例子和測試程式碼,在使用javacv時可能會出現std::exception: bad allocation。原因是在java中需要顯示地呼叫記憶體釋放方法釋放記憶體。 如: Mat img = Highgui.imread(fileName, H
好幾天沒寫了。。。——周三2.27
限制 幸福 知識 這一 進行 手機 比例 小米 時間 周三吧,下午給老師搞pdf,那叫一個不順利。 晚上的溝通與交流也沒能找到一個妹子多的小組,嚶嚶嚶~~~信工男人真可憐,經管男人太幸福~~~ 關於pdf的使用: 最大感想 腦子是個好東西 今天下午,應
從預設解構函式學習c++,new,delete,記憶體洩漏,野指標
預設解構函式:當系統沒有顯式定義解構函式,編譯器同樣會為物件定義一個預設解構函式,預設的解構函式只能釋放普通資料成員所佔用的空間,無法通過釋放通過new和malloc進行申請的空間,因此避免記憶體洩漏,我們要顯式的解構函式對申請的空間釋放。 記憶體洩漏(Memory Leak)是指程式中己動態分配的堆記憶體
花了兩天時間學習了 sass, less, stylus的基本語法和簡單使用, 談談感受.
列表 stylus 單點 blank 明顯 一點 css 自動編譯 數據 花了兩天時間學習了 sass, less, stylus的基本語法和簡單使用, 談談感受. 1. 變量的問題 1.1變量的表示 sass有個$var, [email protected]/
【C#公共幫助類】JsonHelper 操作幫助類, 以後再也不用滿地找Json了,拿來直接用
using System; using System.Collections.Generic; using System.Text; using System.Data; using System.Linq; using System.Web.Script.Serialization; usi