1. 程式人生 > >Netty記憶體洩漏檢測機制

Netty記憶體洩漏檢測機制

廣泛使用直接記憶體是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的使用狀態。