考虑到buffer释放的复杂性,理论上总是有可能因为某些失误导致有buffer没有正确释放从而发生内存泄露的可能.
为此netty提供了资源泄露检测机制,在AbstractByteBuf中提供了一个ResourceLeakDetector的实例leakDetector来做资源泄露检测.
public abstract class AbstractByteBuf extends ByteBuf {
static final ResourceLeakDetector<ByteBuf> leakDetector = new ResourceLeakDetector<ByteBuf>(ByteBuf.class);
}
工作原理
为了检测资源是否泄露,netty中使用了PhantomReference(虚引用)和ReferenceQueue(引用队列).
工作原理如下:
- 根据检测级别和采样率的设置, 在需要时为需要检测的ByteBuf创建PhantomReference
- 当JVM回收掉ByteBuf对象时,JVM会将PhantomReference放入ReferenceQueue
- 通过对ReferenceQueue中PhantomReference的检查,判断在GC前是否有释放ByteBuf的资源,就可以知道是否有资源释放
实现机制
理论上, ByteBuf应该做两个事情:
- 告之leakDetector需要监控的所有对象 在创建时应该调用leakDetector.open()方法,传入自身(也就是this)的引用.这样leakDetector就可以通过PhantomReference + ReferenceQueue 的机制来监控这些对象. 为了后面能调用close方法,这里需要保存返回的ResourceLeak对象.
- 当被监控的对象正常释放时告之leakDetector 在ByteBuf回收时,应该调用ResourceLeak.close()方法来告之.
以类CompositeByteBuf为例:
public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable<ByteBuf> {
private final ResourceLeak leak;
public CompositeByteBuf(ByteBufAllocator alloc, boolean direct, int maxNumComponents) {
......
leak = leakDetector.open(this);
}
}
protected void deallocate() {
......
if (leak != null) {
leak.close();
}
}
代码实现
open()
ResourceLeakDetector的open()方法生成leakDetector对象:
public final class ResourceLeakDetector<T> {
public ResourceLeak open(T obj) {
......
return new DefaultResourceLeak(obj);
}
}
这个leakDetector对象实际是一个PhantomReference, 注意构造函数的实现,refQueue是前面所说的ReferenceQueue:
private final class DefaultResourceLeak extends PhantomReference<Object> implements ResourceLeak {
DefaultResourceLeak(Object referent) {
super(referent, referent != null? refQueue : null);
}
}
close()
ResourceLeakDetector的close()方法,根据内部的freed属性来判断是否是第一次调用,然后返回true/false:
public boolean close() {
if (freed.compareAndSet(false, true)) {
......
return true;
}
return false;
}
判断的基本的原则:
- 如果ByteBuf在GC前被正常释放,那么就会正常调用close()方法,这样下一次再调用close时会返回false
- 如果ByteBuf被GC前未能正常释放, close()方法没能及时调用,那么再调用close()时会返回true
reportLeak()
检测的方式就是从refQueue中获取已经被GC了的DefaultResourceLeak对象,然后按照上面的逻辑调用close()方法做检测:
private void reportLeak(Level level) {
for (;;) {
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
ref.clear();
if (!ref.close()) {
// 返回false, 说明之前已经调用郭,资源被正常释放
continue;
}
// 发现泄露了,在这里报错!
}
}
详尽的代码分析,请见下节.