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 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的使用状态。

你可能感兴趣的:(Netty,java)