广泛使用直接内存是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的使用状态。