Netty源碼解析——Buffer之ByteBuf 內(nèi)存泄漏檢測(cè)
0.引用計(jì)數(shù)器基礎(chǔ)知識(shí)
1)? 計(jì)數(shù)器基于AtomicIntegerFieldUpdater,因?yàn)锽yteBuf對(duì)象很多,如果都把int包一層AtomicInteger花銷較大从撼,而AtomicIntegerFieldUpdater只需要一個(gè)全局的靜態(tài)變量租副。所有的ByteBuf的引用計(jì)數(shù)器初始值為1.
private static final AtomicIntegerFieldUpdater<AbstractReferenceCounted> refCntUpdater =
? ? ?? AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCounted.class, "refCnt");
private volatile int refCnt = 1;
拓展:AtomicIntegerFieldUpdater:允許類中里面一個(gè)字段具有原子性践叠。這個(gè)字段必須是volatile類型的簿透,在線程間共享變量時(shí)保證其可見性.字段描述類型是與調(diào)用者與操作對(duì)象字段的關(guān)系一致,可以進(jìn)行反射進(jìn)行原子操作.對(duì)于父類的字段,子類不能直接操作,只能是實(shí)例變量和可修改的變量,不能再被修飾的類前面加 static 和final 的修飾符. 比如refCnt不能加static和final修飾符,如果是包裝類Integer和Long的化可以使用AtomicReferenceFieldUpdater類.
2)? release()和retain() 分別對(duì)應(yīng)著計(jì)數(shù)器refCnt加1和減1.等于零時(shí)deallocate()被調(diào)用進(jìn)行回收.由于每次操作只能操作的數(shù)為1,居然在程序里面直接判斷old值為1時(shí)就進(jìn)行deallocate(),為什么這么寫最蕾?當(dāng)引用計(jì)數(shù)器為0時(shí),底下的buffer已經(jīng)回收,即使ByteBuf對(duì)象還在祝拯,對(duì)它的各種訪問(wèn)操作都會(huì)拋出異常士袄。
3)? 有duplicate(),slice(),order()所衍生出來(lái)的ByteBuf與原對(duì)象共享底下的buffer,也共享引用計(jì)數(shù)器,所以它們經(jīng)常需要調(diào)用retain()來(lái)顯示自己的存在泻拦。copy()方法生成的ByteBuf完全獨(dú)立于原ByteBuf堤如,而slice()和duplicate()方法生成的ByteBuf與原ByteBuf共享相同的底層實(shí)現(xiàn),但是各自維護(hù)獨(dú)立的索引和標(biāo)記.slice()方法從原始的ByteBuf中截取一段,這段數(shù)據(jù)從readerIndex到writeIndex蒲列,返回時(shí)新的ByteBuf的最大容量maxCapacity為原始ByteBuf的readableBytes().而duplicate()則是把整個(gè)ByteBuf截取出來(lái).在一個(gè)函數(shù)體里面,只要增加了引用計(jì)數(shù)就必須調(diào)用release()方法.
1.Netty內(nèi)存泄漏檢測(cè)
內(nèi)存泄漏,主要是針對(duì)池化的ByteBuf.ByteBuf對(duì)象被JVM GC掉之前,沒(méi)有調(diào)用release()把底下的DirectByteBuffer或byte[]歸還到池里,會(huì)導(dǎo)致池越來(lái)越大.Netty默認(rèn)會(huì)從分配的ByteBuf里抽樣出大約1%的來(lái)進(jìn)行跟蹤窒朋。如果泄漏,會(huì)有如下語(yǔ)句打蛹掂帧:
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()
禁用(DISABLED) - 完全禁止泄露檢測(cè)炼邀,省點(diǎn)消耗。
簡(jiǎn)單(SIMPLE) - 默認(rèn)等級(jí)剪侮,告訴我們?nèi)拥?%的ByteBuf是否發(fā)生了泄露拭宁,但總共一次只打印一次,看不到就沒(méi)有了瓣俯。
高級(jí)(ADVANCED) - 告訴我們?nèi)拥?%的ByteBuf發(fā)生泄露的地方杰标。每種類型的泄漏(創(chuàng)建的地方與訪問(wèn)路徑一致)只打印一次。對(duì)性能有影響彩匕。
偏執(zhí)(PARANOID) - 跟高級(jí)選項(xiàng)類似腔剂,但此選項(xiàng)檢測(cè)所有ByteBuf,而不僅僅是取樣的那1%驼仪。對(duì)性能有絕大的影響掸犬。
? ? ByteBufAllocator 可用于創(chuàng)建 ByteBuf 對(duì)象。創(chuàng)建的過(guò)程中绪爸,它會(huì)調(diào)用 #toLeakAwareBuffer(...) 方法湾碎,將 ByteBuf 裝飾成 LeakAware ( 可檢測(cè)內(nèi)存泄露 )的 ByteBuf 對(duì)象.
1.1 ResourceLeakDetector
ResourceLeakDetector 為了檢測(cè)內(nèi)存是否泄漏,使用了 WeakReference( 弱引用 )和 ReferenceQueue( 引用隊(duì)列 )奠货,過(guò)程如下:
根據(jù)檢測(cè)級(jí)別和采樣率的設(shè)置介褥,在需要時(shí)為需要檢測(cè)的 ByteBuf 創(chuàng)建WeakReference 引用。
當(dāng) JVM 回收掉 ByteBuf 對(duì)象時(shí)递惋,JVM 會(huì)將 WeakReference 放入ReferenceQueue 隊(duì)列中柔滔。
通過(guò)對(duì) ReferenceQueue 中 WeakReference 的檢查,判斷在 GC 前是否有釋放ByteBuf 的資源萍虽,就可以知道是否有資源釋放睛廊。
private final ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks = PlatformDependent.newConcurrentHashMap();//DefaultResourceLeak集合
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();//引用隊(duì)列
private final ConcurrentMap<String, Boolean> reportedLeaks = PlatformDependent.newConcurrentHashMap();//已匯報(bào)的內(nèi)存泄露的資源類型的集合
private final String resourceType;//資源類型
private final int samplingInterval;//采樣頻率
1.2 ResourceLeakTracker
每個(gè)資源( 例如:ByteBuf 對(duì)象 ),會(huì)創(chuàng)建一個(gè)追蹤它是否內(nèi)存泄露的 ResourceLeakTracker 對(duì)象,代碼如下
leak = AbstractByteBuf.leakDetector.track(buf);
if (leak != null) {
?? buf = new AdvancedLeakAwareByteBuf(buf, leak);
}
1.3? DefaultResourceLeak
DefaultResourceLeak 繼承 java.lang.ref.WeakReference 類杉编,實(shí)現(xiàn) ResourceLeakTracker 接口超全,默認(rèn) ResourceLeakTracker 實(shí)現(xiàn)類。同時(shí),它是 ResourceLeakDetector 內(nèi)部靜態(tài)類.
private static final class DefaultResourceLeak<T>
? ? ?? extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak
構(gòu)造函數(shù)
DefaultResourceLeak(
? ? ?? Object referent,
? ? ?? ReferenceQueue<Object> refQueue,
? ? ?? ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks) {
?? super(referent, refQueue);
?
?? trackedHash = System.identityHashCode(referent);
?? allLeaks.put(this, LeakEntry.INSTANCE);
?? // Create a new Record so we always have the creation stacktrace included.
?? headUpdater.set(this, new Record(Record.BOTTOM));
?? this.allLeaks = allLeaks;
}
[拓展]:引用隊(duì)列(Reference Queue)
一旦弱引用對(duì)象開始返回null王财,該弱引用指向的對(duì)象就被標(biāo)記成了垃圾卵迂。而這個(gè)弱引用對(duì)象(非其指向的對(duì)象)就沒(méi)有什么用了。通常這時(shí)候需要進(jìn)行一些清理工作绒净。比如WeakHashMap會(huì)在這時(shí)候移除沒(méi)用的條目來(lái)避免保存無(wú)限制增長(zhǎng)的沒(méi)有意義的弱引用见咒。
引用隊(duì)列可以很容易地實(shí)現(xiàn)跟蹤不需要的引用。當(dāng)你在構(gòu)造WeakReference時(shí)傳入一個(gè)ReferenceQueue對(duì)象挂疆,當(dāng)該引用指向的對(duì)象被標(biāo)記為垃圾的時(shí)候改览,這個(gè)引用對(duì)象會(huì)自動(dòng)地加入到引用隊(duì)列里面下翎。接下來(lái),你就可以在固定的周期宝当,處理傳入的引用隊(duì)列视事,比如做一些清理工作來(lái)處理這些沒(méi)有用的引用對(duì)象。
當(dāng)referent為 ByteBuf 對(duì)象時(shí),如果它被正確的釋放庆揩,即調(diào)用了release()方法俐东,從而調(diào)用了 AbstractReferenceCountedByteBuf 的closeLeak()方法,最終調(diào)用到 ResourceLeakTracker#close(trackedByteBuf)方法,那么該 ByteBuf 對(duì)象對(duì)應(yīng)的 ResourceLeakTracker 對(duì)象订晌,將從ResourceLeakDetector.allLeaks中移除虏辫。在ResourceLeakDetector#reportLeak()方法中,即使從refQueue隊(duì)列中锈拨,獲取到該 ByteBuf 對(duì)象對(duì)應(yīng) ResourceLeakTracker 對(duì)象砌庄,因?yàn)樵赗esourceLeakDetector.allLeaks中移除了,所以在ResourceLeakDetector#reportLeak()!ref.dispose() = true奕枢,continue 就不報(bào)告內(nèi)存泄漏了娄昆。