Netty記憶體洩漏檢測機制
阿新 • • 發佈:2019-01-02
廣泛使用直接記憶體是Netty成為高效網路框架的原因之一。然而,直接記憶體釋放並不受GC的控制,Netty中的對於直接記憶體的使用類似與C語言中(malloc、free),需要開發者手動分配和回收記憶體,而JVM GC只負責回收JAVA堆上的引用以及堆中記憶體。所有直接記憶體使用中,需要在JVM GC回收buf之前,手動呼叫release()方法去釋放直接記憶體,否則存在記憶體洩漏。因此,在Netty中,在使用直接記憶體時,引入了記憶體洩漏檢測機制以便開發者及時發現記憶體的洩漏。
在Netty相關Direct和基於快取的Pool的記憶體相應原始碼中,通常呼叫toLeakAwareBuffer(buf);該方法具體定義在AbstractByteBufAllocator,對應實現如下:
protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
ResourceLeakTracker<ByteBuf> leak;
switch (ResourceLeakDetector.getLevel()) {
/*
** 根據檢測界別,建立不同型別的記憶體洩漏檢測器
*/
case SIMPLE:
leak = AbstractByteBuf.leakDetector.track(buf);//資源檢測器監控buf使用
if (leak != null ) {
buf = new SimpleLeakAwareByteBuf(buf, leak);//裝飾器將buf包裝
}
break;
case ADVANCED:
case PARANOID:
leak = AbstractByteBuf.leakDetector.track(buf);
if (leak != null) {
buf = new AdvancedLeakAwareByteBuf(buf, leak);
}
break ;
default:
break;
}
return buf;
}
在進行記憶體監控時,呼叫leakDetector的track方法將buf監控起來,並將對應檢測器包裝至buf以監控使用狀態。在對buf包裝時,會根據具體的監控級別,對應不同的包裝類,其監控實現主要通過ResourceLeakDetector。在ResourceLeakDetector的track(buf),只是簡單包裝為track0(buf),程式碼如下:
private DefaultResourceLeak track0(T obj) {
Level level = ResourceLeakDetector.level;
if (level == Level.DISABLED) {//關閉記憶體使用監控,直接返回
return null;
}
if (level.ordinal() < Level.PARANOID.ordinal()) {
if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {//以固定samplingInterval取樣間隔報告記憶體使用情況
reportLeak(level);//包裝記憶體使用
return new DefaultResourceLeak(obj);//返回obj對應的監控器,以便被buf包裝
} else {
return null;
}
} else {
reportLeak(level);
return new DefaultResourceLeak(obj);
}
}
track0以固定的間隔去報告buf記憶體使用狀態,同時返回buf對應的檢測器
private void reportLeak(Level level) {
if (!logger.isErrorEnabled()) {//禁止Error級別日誌時
for (;;) {
@SuppressWarnings("unchecked")
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {//引用佇列為空,返回{無記憶體洩漏}
break;
}
ref.close();
}
return;
}
// Detect and report previous leaks.
for (;;) {
@SuppressWarnings("unchecked")
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {//引用佇列為空(沒有buf被GC)直接返回
break;
}
ref.clear();//清除引用
if (!ref.close()) {//沒有記憶體洩漏
continue;
}
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {//buf存在洩漏
if (records.isEmpty()) {
reportUntracedLeak(resourceType);//日誌輸出buf型別的洩漏
} else {
reportTracedLeak(resourceType, records);//日誌輸出具體buf的洩漏
}
}
}
}
Netty通過虛引用與引用佇列,檢測GC之前buf的release(){亦是ref.close()返回true}被呼叫;此外,在開啟使用buf的過程中呼叫檢測器的record(Object)即可記錄buf的使用狀態。