當前位置:編程學習大全網 - 編程語言 - Netty源碼-內存泄漏檢測toLeakAwareBuffer

Netty源碼-內存泄漏檢測toLeakAwareBuffer

Netty在實現 ByteBuf 時采用了引用計數法進行 ByteBuf 的回收,使用引用計數法進行回收的 ByteBuf 都擴展了 AbstractReferenceCountedByteBuf 類,在使用 AbstractReferenceCountedByteBuf 時需要調用 AbstractReferenceCountedByteBuf.retain 方法遞增引用計數器,在使用完畢時則需要調用 AbstractReferenceCountedByteBuf.release 方法遞減引用計數器,當計數器為 0 時,會進行 ByteBuf 的回收工作:池化的 ByteBuf 不會進行實際的內存釋放,會將占用的內存歸還給內存池,非池化的 ByteBuf 則會直接釋放內存(為了敘述簡單,後面釋放內存則指真正釋放內存或者將內存歸還給內存池)。

通過上面的描述可知, ByteBuf 的正確回收依賴 retain 和 release 方法的正確調用,內存提前釋放(即在使用 ByteBuf 時沒有調用 retain 方法,導致提前釋放)應用會報錯,用戶也能及時感知到;但是如果使用完 ByteBuf 忘了調用 release 則會導致內存不能及時得到回收,造成內存泄漏,且內存泄漏用戶無法及時感知,久而久之就會發生OOM。為了解決這種問題,Netty采用了內存泄漏檢測機制,發生內存泄漏時會通過日誌將內存泄漏信息打印出來,報告給用戶。

Netty的內存泄漏檢測使用了 WeakReference ,即弱引用,了解過Java四種引用類型(強、軟、弱、虛)和引用隊列( ReferenceQueue )的讀者知道,弱引用持有的對象會在虛擬機觸發GC時(不管回收之後內存是否夠用)被回收掉,如果使用具有引用隊列參數的構造函數實例化 WeakReference 時,弱引用持有的對象在GC被回收時,弱引用自身會被放入引用隊列。

為了後面能更好的理解Netty內存泄漏檢測的細節,下面先看幾個弱引用的例子,在下面的幾個例子中,我們使用的數據類和自定義的弱引用類子類如下:

好了,三個例子已經介紹完畢,後面在介紹Netty內存泄漏檢測時就使用了這裏的例子結果,在具體介紹時會和這裏的例子壹壹對應。

Netty中將普通 ByteBuf 轉為具有內存泄漏檢測功能的 ByteBuf 是通過 AbstractByteBufAllocator.toLeakAwareBuffer 方法實現的,我們直接在Eclipse中看該方法的調用層次即可知道Netty在哪裏對 ByteBuf 進行了轉換,該方法調用如下圖所示:

可見池化內存分配器在分配heap或者direct ByteBuf 時都進行了轉換,非池化內存分配器僅在分配direct ByteBuf 時進行了轉換。個人理解時采用池化內存需要特別關註內存釋放,否則為了實現池化內存預先分配的壹大塊內存會因為沒有釋放被很快分配完,造成後面沒有內存進行分配。非池化分配的直接內存也需要特別註意釋放,放置內存泄漏;非池化分配的heap內存(其實就是壹個 byte 數組)則可以在對象被回收時同時被回收掉,發生內存泄漏的可能性較小。

本節介紹Netty中內存泄漏檢測相關的類,僅做壹個大致介紹,類中的重要方法我們放在後面介紹。

主要負責使用 track 方法對指定的 ByteBuf 進行內存檢測泄漏進行追蹤,並返回負責追蹤的 ResourceLeakTracker 類實例,同時在調用 track 方法時,也會根據指定的檢測級別匯報最近的內存泄漏檢測結果。該類由工廠類 ResourceLeakDetectorFactory 負責實例化,默認的實現為 ResourceLeakDetector ,在 ResourceLeakDetectorFactory 類的默認實現 DefaultResourceLeakDetectorFactory 中,也會根據用戶是否配置了 io.netty.customResourceLeakDetector 來決定采用默認實現 ResourceLeakDetector 還是使用用戶自定義的 ResourceLeakDetector ,用戶自定義的 ResourceLeakDetector 必須是其子類。

默認實現為 DefaultResourceLeak , DefaultResourceLeak 實現了 ResourceLeakTracker 和 ResourceLeak 接口,同時也繼承了類 WeakReference ,是壹個弱引用實現。首先,同上面 例2 的結果壹樣,如果在使用 ByteBuf 時忘了調用 AbstractReferenceCountedByteBuf.release 方法,那麽將不會調用 DefaultResourceLeak.clear 方法去手動清空該弱引用持有的實際對象,在發生GC時,會由垃圾收集器對弱引用持有的實際對象進行回收,即發生了內存泄漏,同時該弱引用自身也會被加入到引用隊列中,該引用隊列是 ResourceLeakDetector 的成員域,上面介紹 ResourceLeakDetector 類時說到該類會在用戶 track 指定 ByteBuf 是匯報檢測結果,該類的匯報數據來源就是引用隊列。 DefaultResourceLeak 同時還提供了 record 方法可以讓用戶在指定時機選擇調用,這個方法可以記錄用戶的調用軌跡(堆棧)。 Record 同時也是壹種單鏈表,在 DefaultResourceLeak 中就使用單鏈表記錄用戶的調用軌跡。

DefaultResourceLeak 供用戶記錄程序調用軌跡的類,也就是 DefaultResourceLeak.record 方法返回的對象,繼承自 Throwable ,因此可以使用 Throwable.getStackTrace 方法獲得調用軌跡信息,打印在內存泄漏報告中可以讓用戶更好的排除內存泄漏問題。

在上面介紹 ResourceLeakTracker 時,說到其默認實現為 DefaultResourceLeak , DefaultResourceLeak 提供了 record 方法記錄用戶的調用軌跡,用戶可在調用 ByteBuf 方法時調用 record 方法記錄調用軌跡,調用的頻率越多,後面在匯報內存泄漏情況時就能打印出越詳細的信息,這樣也能更方便的排查問題。

Netty提供了兩個 ByteBuf 的封裝類供選擇,就對應不同的 record 調用頻率,每個封裝類都持有 ResourceLeakTracker 對象,Netty根據配置的內存檢測級別(下壹節介紹相關配置參數)使用不同的 ByteBuf 封裝類。

Netty提供的兩個 ByteBuf 封裝類就是 SimpleLeakAwareCompositeByteBuf 和 AdvancedLeakAwareCompositeByteBuf , AdvancedLeakAwareCompositeByteBuf 是 SimpleLeakAwareCompositeByteBuf 的子類, SimpleLeakAwareCompositeByteBuf 類僅僅持有 ResourceLeakTracker 對象,但是看其源碼,發現沒有調用過 record 方法,所以只能知道是否發生了內存泄漏時,無法打印出任何調用軌跡信息。 AdvancedLeakAwareCompositeByteBuf 作為 SimpleLeakAwareCompositeByteBuf 的子類,在 ByteBuf 的多個方法中調用了 record 方法,所以在發生內存泄漏時,能夠打印出比較詳細的調用軌跡信息。

在 AdvancedLeakAwareCompositeByteBuf 類中使用了配置參數 io.netty.leakDetection.acquireAndReleaseOnly 來控制是否只是在調用增加或減少引用計數器的方法時才調用 record 方法記錄調用軌跡,默認為false。 AdvancedLeakAwareCompositeByteBuf 中 retain 和 release 方法因為改變了引用計數器就直接調用了 record 方法,而該類中的其他方法則根據 io.netty.leakDetection.acquireAndReleaseOnly 的配置決定是否調用 record 方法,這裏為了節省篇幅就不列出 AdvancedLeakAwareCompositeByteBuf 類中調用 record 的方法了,讀者可自行查看。

在介紹相關配置參數之前,我們先看下Netty提供的內存泄漏檢測級別:

Level.ADVANCED 和 Level.PARANOID 使用的 ByteBuf 包裝類都是 AdvancedLeakAwareCompositeByteBuf ,我們上面介紹 ResourceLeakDetector 類時提到該類使用 track 方法對指定的 ByteBuf 進行內存檢測泄漏進行追蹤,並返回負責追蹤的 ResourceLeakTracker 類實例,同時在調用 track 方法時,也會根據指定的檢測級別匯報最近的內存泄漏檢測結果。如果內存泄漏檢測級別為 Level.PARANOID 時則每次調用 track 方法都會進行內存泄漏報告;如果級別為 Level.ADVANCED 或者 Level.SIMPLE 則會以壹定頻率進行內存泄漏報告,而不是每次 track 都進行報告。

是否關閉Netty內存泄漏檢測功能,默認為false。如果該參數配置為false,則默認的內存泄漏檢測級別根據此參數的配置為 Level.DISABLED ,否則默認的級別為 Level.SIMPLE 。

配置內存泄漏檢測級別的參數,用於老版本的配置參數。

新的內存泄漏檢測級別參數,如果沒有配置,則會采用老版本參數配置的級別作為最終配置。

在第4節介紹內存泄漏檢測相關類時,我們介紹過 DefaultResourceLeak 提供了 record 方法記錄用戶的調用軌跡,如果當前保存的調用軌跡記錄數 Record 大於參數 io.netty.leakDetection.targetRecords 配置的值,那麽會以壹定的概率(1/2^n)刪除頭結點之後再加入新的記錄,當然也有可能不刪除頭結點直接新增新的記錄。

該參數的默認為4。

上面介紹過,在 AdvancedLeakAwareCompositeByteBuf 類中使用了配置參數 io.netty.leakDetection.acquireAndReleaseOnly 來控制是否只是在調用增加或減少引用計數器的方法時才調用 record 方法記錄調用軌跡,默認為false。

在介紹 ResourceLeakDetector 類時提到過,默認的 ResourceLeakDetector 類就是 ResourceLeakDetector ,但是用戶可以使用參數 io.netty.customResourceLeakDetector 來決定采用默認實現 ResourceLeakDetector 還是使用用戶自定義的 ResourceLeakDetector 。

我們在第二節介紹了Netty中將普通 ByteBuf 轉為具有內存泄漏檢測功能的 ByteBuf 是通過 AbstractByteBufAllocator.toLeakAwareBuffer 方法實現的。

這裏我們先看下該方法的源碼:

上面的源碼中是調用 AbstractByteBuf.leakDetector.track(buf) 返回 ResourceLeakTracker 類對象的,這裏我們看下默認的 ResourceLeakDetector 中 track 方法實現:

我們看到 AbstractByteBufAllocator.toLeakAwareBuffer 對 ResourceLeakDetector.track 返回的 DefaultResourceLeak 和傳入的 ByteBuf 對象進行封裝,返回了具有內存泄漏檢測功能的 ByteBuf 封裝類 SimpleLeakAwareCompositeByteBuf 或其子類 AdvancedLeakAwareCompositeByteBuf 。如果應用程序在使用 ByteBuf 正確調用了 retain 和 release 方法,則在引用計數器為0時,則會清除弱引用持有的實際對象,發生GC時, DefaultResourceLeak 也不會被放入引用隊列中(見前面第2節 例3 結果)。

但是如果應用程序在使用 ByteBuf 沒有正確調用 retain 和 release 方法,則不會清除弱引用持有的實際對象,此時如果實際上已經沒有強引用指向該 ByteBuf ,那麽在發生GC時,垃圾收集器會回收該 ByteBuf ,而弱引用 DefaultResourceLeak 會被放入引用隊列中(見前面第2節 例2 結果),加入到引用隊列中的就是識別到的發生內存泄漏的 ByteBuf 。在 ResourceLeakDetector.track 方法中調用的 reportLeak 輸出的就是引用隊列中的弱引用 DefaultResourceLeak :

到這裏,已經基本上介紹完Netty內存檢測的實現原理,下面我們再看下 DefaultResourceLeak.record 是如何記錄調用軌跡的:

最後我們再看下 Record 是如何輸出調用軌跡的,前面我們說到 Record 繼承自類 Throwable ,因此可使用 getStackTrace 方法獲取實例化該對象時的調用軌跡,所以上面在輸出內存泄漏報告時就調用了 Record.toString 方法:

  • 上一篇:trlcon系統主要用於什麽控制?
  • 下一篇:假如再讓妳上壹次大學,妳會怎麽度過?
  • copyright 2024編程學習大全網