概览
Netty为了提高性能,自己写了buffer模块,用于存储网络请求的数据。
Netty提供了分配和释放的函数,跟c的malloc和free一样,控制权在用户手里,如果用户用完后忘记调用release函数,会造成该块内存无法被回收,造成内存泄露。
Netty应用WeakReference(新版本修改成PhantomReference),来对内存泄露进行检测。
主要原理
- Netty分配ByteBuf后,调用toLeakAwareBuffer方法进行包装,这个过程结束后,ByteBuf的实例会同时由SimpleLeakAwareByteBuf(Bytebuf的包装器)和DefaultResourceLeak(泄露追踪器)持有。
- DefaultResourceLeak是WeakReference的实例,所以当SimpleLeakAwareByteBuf被GC清理后,ByteBuf也会被GC清理。但是DefaultResourceLeak可以通过ReferenceQueue得到通知。
- DefaultResourceLeak继承至WeakReference,并添加了其持有的ByteBuf的释放标识;SimpleLeakAwareByteBuf的release方法会去清理DefaultResourceLeak的释放标识,从而被ReferenceQueue通知的时候,能够判断是否有内存泄露
主要代码片段
PooledByteBufAllocator部分代码
protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
# 代码有删减
final ByteBuf buf;
buf = heapArena.allocate(cache, initialCapacity, maxCapacity);
# 通过SimpleLeakAwareByteBuf包装ByteBuf
return toLeakAwareBuffer(buf);
}
protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
# 代码有删减
ResourceLeakTracker<ByteBuf> leak;
# AbstractByteBuf.leakDetector获取泄露监测器,目前只有一种实现:ResourceLeakDetector
leak = AbstractByteBuf.leakDetector.track(buf);
# SimpleLeakAwareByteBuf同时持有ByteBuf和ResourceLeakTracker
buf = new SimpleLeakAwareByteBuf(buf, leak);
return buf;
}
SimpleLeakAwareByteBuf部分代码
@Override
public boolean release() {
if (super.release()) {
closeLeak();
return true;
}
return false;
}
private void closeLeak() {
boolean closed = leak.close(trackedByteBuf);
assert closed;
}
ResourceLeakDetector部分代码
public final ResourceLeakTracker<T> track(T obj) {
return track0(obj);
}
private DefaultResourceLeak track0(T obj) {
# 代码有删减
Level level = ResourceLeakDetector.level;
if (level.ordinal() < Level.PARANOID.ordinal()) {
# 计算抽样率,符合抽样率后在对内存泄露进行追踪
if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {
reportLeak();
return new DefaultResourceLeak(obj, refQueue, allLeaks);
}
return null;
}
# 最高level会每次都会去检查内存泄露,reportLeak方法就是去检查refQueue
# 中的DefaultResourceLeak的释放标识
reportLeak();
# DefaultResourceLeak是WeakReference的之类
return new DefaultResourceLeak(obj, refQueue, allLeaks);
}
private void reportLeak() {
for (;;) {
# DefaultResourceLeak持有的ByteBuf被GC后得到通知
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
# 判断DefaultResourceLeak的释放标识,如果释放过说明SimpleLeakAwareByteBuf调用过release
# 回收了其持有的ByteBuf,否则进行下面的日志打印
if (!ref.dispose()) {
continue;
}
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
if (records.isEmpty()) {
reportUntracedLeak(resourceType);
} else {
reportTracedLeak(resourceType, records);
}
}
}
}
DefaultResourceLeak extends WeakReference部分代码
@Override
public boolean close() {
if (allLeaks.remove(this, LeakEntry.INSTANCE)) {
clear();
headUpdater.set(this, null);
return true;
}
return false;
}
@Override
public boolean close(T trackedObject) {
assert trackedHash == System.identityHashCode(trackedObject);
return close() && trackedObject != null;
}
boolean dispose() {
clear();
return allLeaks.remove(this, LeakEntry.INSTANCE);
}
其他要点
记录调用堆栈
Record extends Throwable
Record是Throwable的子类,DefaultResourceLeak的构造函数中,会创建Record,到时候生内存泄露时,可以通过持有的Record打印创建分配内存时的堆栈信息,辅助定位
思考:通过java的Reference机制能够检测到内存泄漏,为啥Netty不利用这个时机,去回收内存
DefaultResourceLeak是WeakReference的实例,他是弱引用ByteBuf。当通过ReferenceQueue得到通知时,DefaultResourceLeak持有的ByteBuf已经被回收了,不能调用ByteBuf的release了。