在io.netty.buffer.AbstractByteBufAllocator类中有如下代码
//装饰器模式,用SimpleLeakAwareByteBuf或AdvancedLeakAwareByteBuf来包装原始的ByteBuf
//两个包装类均通过调用ResourceLeak的record方法来记录ByteBuf的方法调用堆栈,区别在于后者比前者记录更多的内容
protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
ResourceLeak leak;
//根据设置的Level来选择使用何种包装器
switch (ResourceLeakDetector.getLevel()) {
case SIMPLE:
//创建用于跟踪和表示内容泄露的ResourcLeak对象
leak = AbstractByteBuf.leakDetector.open(buf);
if (leak != null) {
//只在ByteBuf.order方法中调用ResourceLeak.record
buf = new SimpleLeakAwareByteBuf(buf, leak);
}
break;
case ADVANCED:
case PARANOID:
leak = AbstractByteBuf.leakDetector.open(buf);
if (leak != null) {
//在ByteBuf几乎所有方法中调用ResourceLeak.record
buf = new AdvancedLeakAwareByteBuf(buf, leak);
}
break;
}
return buf;
}
下图展示了该方法被调用的时机.可见Netty只对PooledByteBuf和DirectByteBuf监控内存泄露.
内存泄露检测
下面观察上述代码中的AbstractByteBuf.leakDetector.open(buf);
实现代码如下//创建用于跟踪和表示内容泄露的ResourcLeak对象
public ResourceLeak open(T obj) {
Level level = ResourceLeakDetector.level;
if (level == Level.DISABLED) {//禁用内存跟踪
return null;
}
if (level.ordinal() < Level.PARANOID.ordinal()) {
//如果监控级别低于PARANOID,在一定的采样频率下报告内存泄露
if (leakCheckCnt ++ % samplingInterval == 0) {
reportLeak(level);
return new DefaultResourceLeak(obj);
} else {
return null;
}
} else {
//每次需要分配 ByteBuf 时,报告内存泄露情况
reportLeak(level);
return new DefaultResourceLeak(obj);
}
}
其中reportLeak方法中完成对内存泄露的检测和报告,如下面代码所示.
private void reportLeak(Level level) {
//......
// 报告生成了太多的活跃资源
int samplingInterval = level == Level.PARANOID? 1 : this.samplingInterval;
if (active * samplingInterval > maxActive && loggedTooManyActive.compareAndSet(false, true)) {
logger.error("LEAK: You are creating too many " + resourceType + " instances. " +
resourceType + " is a shared resource that must be reused across the JVM," +
"so that only a few instances are created.");
}
// 检测并报告之前发生的内存泄露
for (;;) {
@SuppressWarnings("unchecked")
//检查引用队列(为什么通过检查该队列,可以判断是否存在内存泄露)
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {//队列为空,没有未报告的内存泄露或者从未发生内存泄露
break;
}
//清理引用
ref.clear();
if (!ref.close()) {
continue;
}
//通过错误日志打印资源的方法调用记录,并将其保存在reportedLeaks中
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
if (records.isEmpty()) {
logger.error("LEAK: {}.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 '-D{}={}' or call {}.setLevel()",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
} else {
logger.error(
"LEAK: {}.release() was not called before it's garbage-collected.{}",
resourceType, records);
}
}
}
}
综合上面的三段代码,可以看出, Netty 在分配新 ByteBuf 时进行内存泄露检测和报告.
DefaultResourceLeak的声明如下
private final class DefaultResourceLeak extends PhantomReference