Netty 引用计数对象与内存泄漏检测详解
1. 概述
整理 Netty 中引用计数对象(ReferenceCounted)的管理机制以及内存泄漏检测(ResourceLeakDetector)的实现原理。Netty 4 引入引用计数来管理对象生命周期,特别是 ByteBuf,以提升内存分配和释放的性能,取代了传统的垃圾回收机制。
2. ReferenceCounted
io.netty.util.ReferenceCounted 接口定义了引用计数的相关操作:
接口定义:
public interface ReferenceCounted {
/**
* 获得引用计数
*/
int refCnt();
/**
* 增加引用计数 1
*/
ReferenceCounted retain();
/**
* 增加引用计数 n
*/
ReferenceCounted retain(int increment);
/**
* 记录当前访问位置 (hint 为 null)
*/
ReferenceCounted touch();
/**
* 记录当前访问位置 (带额外 hint 信息)
*/
ReferenceCounted touch(Object hint);
/**
* 减少引用计数 1,计数为 0 时释放
*/
boolean release();
/**
* 减少引用计数 n,计数为 0 时释放
*/
boolean release(int decrement);
}
refCnt(): 返回对象的当前引用计数。当引用计数为 0 时,表示对象已被释放。retain()/retain(int increment): 增加对象的引用计数。touch()/touch(Object hint): 用于调试目的,记录对象的当前访问位置和可选的额外信息,这些信息在检测到内存泄漏时会通过ResourceLeakDetector提供。release()/release(int decrement): 减少对象的引用计数,当引用计数降至 0 时,会执行对象的释放操作。
子接口与实现类:
io.netty.buffer.ByteBuf: 所有ByteBuf的实现类都支持引用计数操作。io.netty.util.AbstractReferenceCounted:ReferenceCounted的抽象实现类,供除了ByteBuf之外需要引用计数的类继承,如AbstractHttpData,DefaultFileRegion等。
3. ByteBuf
ByteBuf 继承了 ReferenceCounted 接口,但其方法并未直接在自身实现。实际的实现位于其相关的子类中。
类图关键类:

黄框:
AbstractReferenceCountedByteBuf,实现了引用计数的获取与增减操作。红框:
WrappedByteBuf,实现了ByteBuf的装饰器模式。WrappedCompositeByteBuf,实现了CompositeByteBuf的装饰器模式。绿框:
SimpleLeakAwareByteBuf,SimpleLeakAwareCompositeByteBuf,实现了SIMPLE级别的内存泄漏检测。蓝框:
UnreleasableByteBuf,用于阻止对装饰的ByteBuf的销毁。
带 “Composite” 的类和不带的类实现逻辑基本一致。
3.1 创建 LeakAware ByteBuf 对象
ByteBufAllocator 在创建 ByteBuf 对象时,会调用 #toLeakAwareBuffer(...) 方法,根据 ResourceLeakDetector 的级别将 ByteBuf 装饰成可检测内存泄漏(LeakAware)的对象。
ResourceLeakDetector 利用了 Java 的弱引用(WeakReference)机制。它会为每个需要被跟踪的资源创建一个弱引用,并将其存储在一个 ReferenceQueue 中。当资源不再被强引用时,垃圾回收器会将弱引用加入到 ReferenceQueue 中。
- ResourceLeakDetector 会定期检查 ReferenceQueue,如果发现有新的弱引用被加入,就说明对应的资源可能发生了泄漏。它会记录泄漏资源的信息,例如创建堆栈跟踪、泄漏次数等,并根据配置的级别进行处理。
ResourceLeakDetector中的DefaultResourceLeak继承自WeakReference,在其构造函数中调用了super(referent, refQueue),这就告诉JVM当referent对象不再被强引用时,将这个WeakReference对象(即DefaultResourceLeak实例)放入指定的refQueue队列中。
代码片段:
// AbstractByteBufAllocator.java
protected static ByteBuf toLeakAwareBuffer(ByteBuf buf) {
ResourceLeakTracker<ByteBuf> leak;
switch (ResourceLeakDetector.getLevel()) {
case SIMPLE:
leak = AbstractByteBuf.leakDetector.track(buf);
if (leak != null) {
buf = new SimpleLeakAwareByteBuf(buf, leak);
}
break;
case ADVANCED:
case PARANOID:
leak = AbstractByteBuf.leakDetector.track(buf);
if (leak != null) {
buf = new AdvancedLeakAwareByteBuf(buf, leak);
}
break;
default:
break;
}
return buf;
}
protected static CompositeByteBuf toLeakAwareBuffer(CompositeByteBuf buf) {
// ... 类似逻辑
}
SIMPLE级别:创建SimpleLeakAwareByteBuf或SimpleLeakAwareCompositeByteBuf。ADVANCED和PARANOID级别:创建AdvancedLeakAwareByteBuf或AdvancedLeakAwareCompositeByteBuf。是否创建
LeakAware对象取决于ResourceLeakDetector#track(ByteBuf)是否返回ResourceLeakTracker对象。PARANOID级别保证返回ResourceLeakTracker,ADVANCED级别则以一定概率(默认约 1%)返回。
3.2 AbstractReferenceCountedByteBuf
io.netty.buffer.AbstractReferenceCountedByteBuf 是实现引用计数获取与增减操作的抽象类。
3.2.1 构造方法
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");
private volatile int refCnt;
protected AbstractReferenceCountedByteBuf(int maxCapacity) {
super(maxCapacity);
refCntUpdater.set(this, 1); // 初始引用计数为 1
}
使用
AtomicIntegerFieldUpdater而非AtomicInteger是为了减少对象创建开销,提高性能。初始引用计数被设置为 1。
3.2.2 refCnt
@Override
public int refCnt() {
return refCnt;
}
- 返回当前的引用计数值。
3.2.3 setRefCnt
protected final void setRefCnt(int refCnt) {
refCntUpdater.set(this, refCnt);
}
- 一个不安全的操作,允许子类直接设置引用计数。
3.2.4 retain
@Override
public ByteBuf retain(int increment) {
return retain0(checkPositive(increment, "increment"));
}
private ByteBuf retain0(final int increment) {
int oldRef = refCntUpdater.getAndAdd(this, increment);
if (oldRef <= 0 || oldRef + increment < oldRef) {
refCntUpdater.getAndAdd(this, -increment);
throw new IllegalReferenceCountException(oldRef, increment);
}
return this;
}
原子地增加引用计数。
如果增加前的引用计数小于等于 0,或者增加后发生溢出,则抛出
IllegalReferenceCountException。
3.2.5 release
@Override
public boolean release() {
return release0(1);
}
@Override
public boolean release(int decrement) {
return release0(checkPositive(decrement, "decrement"));
}
private boolean release0(int decrement) {
int oldRef = refCntUpdater.getAndAdd(this, -decrement);
if (oldRef == decrement) {
deallocate(); // 引用计数降至 0,执行释放
return true;
} else if (oldRef < decrement || oldRef - decrement > oldRef) {
refCntUpdater.getAndAdd(this, decrement);
throw new IllegalReferenceCountException(oldRef, -decrement);
}
return false;
}
/**
* 当引用计数为 0 时调用,执行真正的释放操作。
*/
protected abstract void deallocate();
原子地减少引用计数。
当引用计数降至 0 时,调用抽象方法
deallocate()执行真正的资源释放。具体的实现由子类完成。如果减少的值大于当前引用计数,或者减少后发生下溢,则抛出
IllegalReferenceCountException。
3.2.6 touch
@Override
public ByteBuf touch() {
return this;
}
@Override
public ByteBuf touch(Object hint) {
return this;
}
- 在
AbstractReferenceCountedByteBuf中,touch()方法并未实际实现记录操作,实际实现在AdvancedLeakAwareByteBuf中。
3.3 SimpleLeakAwareByteBuf
io.netty.buffer.SimpleLeakAwareByteBuf 继承自 WrappedByteBuf,是 SIMPLE 级别内存泄漏检测的 ByteBuf 实现。
3.3.1 构造方法
private final ByteBuf trackedByteBuf;
final ResourceLeakTracker<ByteBuf> leak;
SimpleLeakAwareByteBuf(ByteBuf wrapped, ResourceLeakTracker<ByteBuf> leak) {
this(wrapped, wrapped, leak);
}
SimpleLeakAwareByteBuf(ByteBuf wrapped, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leak) {
super(wrapped);
this.trackedByteBuf = ObjectUtil.checkNotNull(trackedByteBuf, "trackedByteBuf");
this.leak = ObjectUtil.checkNotNull(leak, "leak");
}
leak: 关联的ResourceLeakTracker对象。trackedByteBuf: 真正与leak关联的ByteBuf对象。在某些场景下,wrapped和trackedByteBuf可能不同。
3.3.2 slice
@Override
public ByteBuf slice() {
return newSharedLeakAwareByteBuf(super.slice());
}
@Override
public ByteBuf slice(int index, int length) {
return newSharedLeakAwareByteBuf(super.slice(index, length));
}
private SimpleLeakAwareByteBuf newSharedLeakAwareByteBuf(ByteBuf wrapped) {
return newLeakAwareByteBuf(wrapped, trackedByteBuf, leak);
}
protected SimpleLeakAwareByteBuf newLeakAwareByteBuf(ByteBuf buf, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leakTracker) {
return new SimpleLeakAwareByteBuf(buf, trackedByteBuf, leakTracker);
}
slice()操作返回新的ByteBuf对象,但不是LeakAware的。因此,需要通过#newSharedLeakAwareByteBuf(...)方法再次装饰成LeakAware的对象。注意,
trackedByteBuf始终是原始的ByteBuf对象,它与leak对象真正关联。duplicate(),readSlice(int length),asReadOnly(),order(ByteOrder endianness)等方法也采用类似的逻辑,在调用父类方法后,将结果再次装饰成LeakAware的对象。
3.3.3 retainedSlice
@Override
public ByteBuf retainedSlice() {
return unwrappedDerived(super.retainedSlice());
}
@Override
public ByteBuf retainedSlice(int index, int length) {
return unwrappedDerived(super.retainedSlice(index, length));
}
// TODO 芋艿,看不懂 1017
private ByteBuf unwrappedDerived(ByteBuf derived) {
// ...
}
retainedSlice()操作返回的slice ByteBuf对象的引用计数会加 1。同样需要通过
#unwrappedDerived(...)方法将其装饰成LeakAware的对象。retainedDuplicate(),readRetainedSlice(int length)等方法也遵循类似的模式。
3.3.4 release
@Override
public boolean release() {
if (super.release()) {
closeLeak(); // 释放完成后关闭 LeakTracker
return true;
}
return false;
}
@Override
public boolean release(int decrement) {
if (super.release(decrement)) {
closeLeak(); // 释放完成后关闭 LeakTracker
return true;
}
return false;
}
private void closeLeak() {
boolean closed = leak.close(trackedByteBuf); // 关闭 ResourceLeakTracker
assert closed;
}
在调用父类的
release()方法释放资源后,会调用#closeLeak()方法关闭关联的ResourceLeakTracker。leak.close(trackedByteBuf)方法会使用trackedByteBuf作为参数,与创建ResourceLeakTracker时使用的对象保持一致。
3.3.5 touch
@Override
public ByteBuf touch() {
return this;
}
@Override
public ByteBuf touch(Object hint) {
return this;
}
SimpleLeakAwareByteBuf同样没有实际实现touch()方法,实际实现在AdvancedLeakAwareByteBuf中。
3.4 AdvancedLeakAwareByteBuf
io.netty.buffer.AdvancedLeakAwareByteBuf 继承自 SimpleLeakAwareByteBuf,用于 ADVANCED 和 PARANOID 级别的内存泄漏检测。
3.4.1 构造方法
AdvancedLeakAwareByteBuf(ByteBuf buf, ResourceLeakTracker<ByteBuf> leak) {
super(buf, leak);
}
AdvancedLeakAwareByteBuf(ByteBuf wrapped, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leak) {
super(wrapped, trackedByteBuf, leak);
}
- 构造方法只是简单地调用父类的构造方法。
3.4.2 retain
@Override
public ByteBuf retain() {
leak.record(); // 记录 retain 操作
return super.retain();
}
@Override
public ByteBuf retain(int increment) {
leak.record(); // 记录 retain 操作
return super.retain(increment);
}
- 在调用父类的
retain()方法前后,会调用leak.record()记录retain操作。
3.4.3 release
@Override
public boolean release() {
leak.record(); // 记录 release 操作
return super.release();
}
@Override
public boolean release(int decrement) {
leak.record(); // 记录 release 操作
return super.release(decrement);
}
- 在调用父类的
release()方法前后,会调用leak.record()记录release操作。
3.4.4 touch
@Override
public ByteBuf touch() {
leak.record(); // 记录 touch 操作
return this;
}
@Override
public ByteBuf touch(Object hint) {
leak.record(hint); // 记录带 hint 的 touch 操作
return this;
}
AdvancedLeakAwareByteBuf终于实现了touch()方法,会调用leak.record()或leak.record(hint)记录访问信息。
3.4.5 recordLeakNonRefCountingOperation
private static final String PROP_ACQUIRE_AND_RELEASE_ONLY = "io.netty.leakDetection.acquireAndReleaseOnly";
private static final boolean ACQUIRE_AND_RELEASE_ONLY;
static {
ACQUIRE_AND_RELEASE_ONLY = SystemPropertyUtil.getBoolean(PROP_ACQUIRE_AND_RELEASE_ONLY, false);
}
static void recordLeakNonRefCountingOperation(ResourceLeakTracker<ByteBuf> leak) {
if (!ACQUIRE_AND_RELEASE_ONLY) {
leak.record();
}
}
- 该静态方法用于判断是否需要记录非引用计数相关的操作(如读写操作)。默认情况下 (
ACQUIRE_AND_RELEASE_ONLY为false),所有ByteBuf的方法都会记录信息。
3.4.6 newLeakAwareByteBuf
@Override
protected AdvancedLeakAwareByteBuf newLeakAwareByteBuf(
ByteBuf buf, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leakTracker) {
return new AdvancedLeakAwareByteBuf(buf, trackedByteBuf, leakTracker);
}
- 重写了父类的方法,确保在
ADVANCED和PARANOID级别下,创建的是AdvancedLeakAwareByteBuf对象。
3.5 UnreleasableByteBuf
io.netty.buffer.UnreleasableByteBuf 继承自 WrappedByteBuf,用于阻止对装饰的 ByteBuf 对象的意外销毁。
- 引用计数操作拦截:
#retain(...),#release(...),#touch(...)等方法被重写,不执行实际的引用计数操作,直接返回自身或false。
@Override
public ByteBuf retain(int increment) {
return this;
}
@Override
public ByteBuf retain() {
return this;
}
@Override
public ByteBuf touch() {
return this;
}
@Override
public ByteBuf touch(Object hint) {
return this;
}
@Override
public boolean release() {
return false;
}
@Override
public boolean release(int decrement) {
return false;
}
- 拷贝操作包装:
#slice(),#duplicate()等拷贝相关的方法被重写,返回的新的ByteBuf对象会被再次包装成UnreleasableByteBuf,以保证其不可释放。
@Override
public ByteBuf slice() {
return new UnreleasableByteBuf(buf.slice());
}
4. ResourceLeakDetector
io.netty.util.ResourceLeakDetector 是 Netty 的内存泄漏检测器。
它使用
WeakReference和ReferenceQueue来跟踪对象的回收情况。当需要检测的
ByteBuf对象被回收时,其对应的WeakReference会被放入ReferenceQueue。通过检查
ReferenceQueue中的WeakReference,可以判断在 GC 发生前资源是否被正确释放。
4.1 静态属性
DEFAULT_LEVEL: 默认的内存检测级别为Level.SIMPLE。Level: 枚举类,定义了内存泄漏检测的级别:DISABLED: 禁用泄漏检测。SIMPLE: 简单采样检测,报告是否存在泄漏(默认,小开销)。ADVANCED: 高级采样检测,报告泄漏对象最近的访问位置(较高开销)。PARANOID: 偏执检测,检测所有对象,报告最近访问位置(最高开销,仅用于测试)。
level: 当前使用的内存泄漏检测级别。通过系统属性-Dio.netty.leakDetection.level或-Dio.netty.leakDetectionLevel配置。DEFAULT_SAMPLING_INTERVAL: 默认采样频率为 128(SIMPLE 和 ADVANCED 级别)。TARGET_RECORDS: 每个DefaultResourceLeak记录的最大Record数量(默认 4)。通过系统属性-Dio.netty.leakDetection.targetRecords配置。静态代码块用于初始化上述静态属性,包括读取系统属性配置和设置默认值。
4.2 构造方法
private final ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks = PlatformDependent.newConcurrentHashMap();
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
private final ConcurrentMap<String, Boolean> reportedLeaks = PlatformDependent.newConcurrentHashMap();
private final String resourceType;
private final int samplingInterval;
public ResourceLeakDetector(Class<?> resourceType, int samplingInterval) {
this(simpleClassName(resourceType), samplingInterval, Long.MAX_VALUE);
}
allLeaks: 存储所有正在追踪的DefaultResourceLeak对象的Map。Value 使用LeakEntry,实际上只关心 Key 的存在性(模拟ConcurrentSet)。refQueue: 引用队列,用于接收被 GC 回收的WeakReference对象(即DefaultResourceLeak)。reportedLeaks: 存储已报告的泄漏信息,避免重复报告相同的泄漏。Key 是泄漏的堆栈信息。resourceType: 被追踪的资源类型名称(简化类名)。samplingInterval: 采样频率。ResourceLeakDetector通常通过ResourceLeakDetectorFactory以工厂模式创建。AbstractByteBuf类中会创建一个全局的ResourceLeakDetector<ByteBuf>实例。
4.3 track
#track(T obj) 方法用于为给定的资源对象创建一个 ResourceLeakTracker 对象,开始追踪其是否发生内存泄漏。
public final ResourceLeakTracker<T> track(T obj) {
return track0(obj);
}
@SuppressWarnings("unchecked")
private DefaultResourceLeak track0(T obj) {
Level level = ResourceLeakDetector.level;
if (level == Level.DISABLED) {
return null;
}
if (level.ordinal() < Level.PARANOID.ordinal()) { // SIMPLE 和 ADVANCED
if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {
reportLeak(); // 报告之前检测到的泄漏
return new DefaultResourceLeak(obj, refQueue, allLeaks); // 创建 DefaultResourceLeak
}
return null;
}
// PARANOID
reportLeak(); // 报告之前检测到的泄漏
return new DefaultResourceLeak(obj, refQueue, allLeaks); // 创建 DefaultResourceLeak
}
DISABLED级别:直接返回null,不进行追踪。SIMPLE和ADVANCED级别:以1 / samplingInterval的概率创建DefaultResourceLeak对象进行追踪。在创建前会调用#reportLeak()报告之前检测到的泄漏。PARANOID级别:总是创建DefaultResourceLeak对象进行追踪,并在创建前调用#reportLeak()。
4.4 reportLeak
#reportLeak() 方法用于检测 refQueue 中是否有待处理的已回收的资源,并报告任何检测到的内存泄漏。
private void reportLeak() {
if (!logger.isErrorEnabled()) {
clearRefQueue(); // 清理队列
return;
}
for (;;) {
@SuppressWarnings("unchecked")
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll(); // 从引用队列中获取已回收的 DefaultResourceLeak
if (ref == null) {
break;
}
if (!ref.dispose()) { // 清理 ref,如果资源未泄漏则返回 false
continue;
}
String records = ref.toString(); // 获取泄漏的堆栈信息
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) { // 相同泄漏信息只报告一次
if (records.isEmpty()) {
reportUntracedLeak(resourceType);
} else {
reportTracedLeak(resourceType, records);
}
}
}
}
如果禁用了错误日志,则无法报告泄漏,直接清理
refQueue并返回。循环处理
refQueue中的DefaultResourceLeak对象。调用
ref.dispose()清理资源,如果返回true,则表示检测到内存泄漏。调用
ref.toString()获取泄漏的堆栈信息。使用
reportedLeaks记录已报告的泄漏信息,避免重复报告。根据是否包含堆栈信息,调用
reportTracedLeak()或reportUntracedLeak()打印错误日志。
4.5 clearRefQueue
#clearRefQueue() 方法用于清理引用队列 refQueue,但不进行任何泄漏报告。
private void clearRefQueue() {
for (;;) {
@SuppressWarnings("unchecked")
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
ref.dispose(); // 清理资源,但不报告
}
}
4.6 addExclusions
#addExclusions(Class clz, String ... methodNames) 方法用于添加需要从泄漏报告的堆栈跟踪中排除的方法。
private static final AtomicReference<String[]> excludedMethods = new AtomicReference<String[]>(EmptyArrays.EMPTY_STRINGS);
public static void addExclusions(Class clz, String ... methodNames) {
// ... 实现逻辑
}
通过
excludedMethods原子引用存储需要排除的方法名和类名。在生成泄漏报告的堆栈跟踪时,会跳过这些排除的方法,使报告更清晰。
Netty 自身在
AbstractByteBufAllocator,AdvancedLeakAwareByteBuf,ReferenceCountUtil中使用了该方法排除特定的内部方法。
5. ResourceLeakTracker
io.netty.util.ResourceLeakTracker 接口定义了资源泄漏追踪器的行为。每个被追踪的资源对象都会关联一个 ResourceLeakTracker。
接口定义:
public interface ResourceLeakTracker<T> {
void record();
void record(Object hint);
boolean close(T trackedObject);
}
record()/record(Object hint): 记录当前调用堆栈,用于在检测到泄漏时定位泄漏发生的最后访问位置。hint参数可以提供额外的调试信息。close(T trackedObject): 关闭泄漏追踪器。当资源被正确释放后,应调用此方法停止追踪。如果重复调用,返回false。
5.1 DefaultResourceLeak
DefaultResourceLeak 是 ResourceLeakTracker 接口的默认实现,同时继承了 java.lang.ref.WeakReference 并实现了 ResourceLeak 接口(Netty 内部接口,标记为资源泄漏)。它是 ResourceLeakDetector 的内部静态类。
5.1.1 构造方法
private static final AtomicReferenceFieldUpdater<DefaultResourceLeak<?>, Record> headUpdater = /* ... */;
private static final AtomicIntegerFieldUpdater<DefaultResourceLeak<?>> droppedRecordsUpdater = /* ... */;
private volatile Record head;
@SuppressWarnings("unused")
private volatile int droppedRecords;
private final ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks;
private final int trackedHash;
DefaultResourceLeak(
Object referent,
ReferenceQueue<Object> refQueue,
ConcurrentMap<DefaultResourceLeak<?>, LeakEntry> allLeaks) {
super(referent, refQueue); // 调用父类 WeakReference 构造方法
assert referent != null;
trackedHash = System.identityHashCode(referent);
allLeaks.put(this, LeakEntry.INSTANCE);
headUpdater.set(this, new Record(Record.BOTTOM)); // 创建初始的尾节点
this.allLeaks = allLeaks;
}
继承
WeakReference的目的是:当追踪的资源对象 (referent) 没有强引用指向时,能够被 GC 回收。当referent被标记为垃圾时,DefaultResourceLeak对象会被添加到传入的refQueue中。trackedHash: 存储被追踪对象的 identity hash code,用于在close()方法中校验传入的对象是否一致。allLeaks: 将当前DefaultResourceLeak对象添加到ResourceLeakDetector的allLeaks集合中,表示正在被追踪。head: 指向Record链表的头部(实际上是最近的Record),用于记录对象的访问历史。初始时创建一个只包含Record.BOTTOM的链表。droppedRecords: 记录由于Record数量超过TARGET_RECORDS而被丢弃的Record数量。
5.1.2 record
#record(...) 方法用于创建一个新的 Record 对象,并将其添加到 head 指向的 Record 链表中,记录当前的访问信息。
@Override
public void record() {
record0(null);
}
@Override
public void record(Object hint) {
record0(hint);
}
private void record0(Object hint) {
if (TARGET_RECORDS > 0) {
Record oldHead;
Record prevHead;
Record newHead;
boolean dropped;
do {
if ((prevHead = oldHead = headUpdater.get(this)) == null) {
return; // 已关闭
}
final int numElements = oldHead.pos + 1;
if (numElements >= TARGET_RECORDS) {
final int backOffFactor = Math.min(numElements - TARGET_RECORDS, 30);
if (dropped = PlatformDependent.threadLocalRandom().nextInt(1 << backOffFactor) != 0) {
prevHead = oldHead.next; // 随机丢弃旧的 head 节点
}
} else {
dropped = false;
}
newHead = hint != null ? new Record(prevHead, hint) : new Record(prevHead); // 创建新 Record,指向之前的 head
} while (!headUpdater.compareAndSet(this, oldHead, newHead)); // CAS 更新 head
if (dropped) {
droppedRecordsUpdater.incrementAndGet(this); // 增加丢弃计数
}
}
}
当
Record的数量超过TARGET_RECORDS时,会以一定的概率随机丢弃旧的head节点(实现了一种指数退避策略,越老的记录越容易被保留)。新的
Record对象被创建并设置为head,指向之前的head节点,形成一个链表。因此,head实际上指向链表的尾部(最近的记录)。使用 CAS 操作保证并发环境下的线程安全。
5.1.3 dispose
#dispose() 方法用于清理 DefaultResourceLeak 对象,并判断是否发生了内存泄漏。
boolean dispose() {
clear(); // 清理对被追踪对象的弱引用
return allLeaks.remove(this, LeakEntry.INSTANCE); // 从 allLeaks 中移除,如果移除成功表示发生泄漏
}
clear(): 清除对被追踪对象的弱引用。allLeaks.remove(this, LeakEntry.INSTANCE): 尝试从allLeaks集合中移除当前的DefaultResourceLeak对象。如果移除成功,说明在 GC 发生前该资源没有被显式释放 (close()方法没有被调用),因此发生了内存泄漏,返回true。
5.1.4 close
#close(T trackedObject) 方法用于关闭当前的 DefaultResourceLeak 对象,停止对其关联资源的泄漏追踪。
@Override
public boolean close(T trackedObject) {
assert trackedHash == System.identityHashCode(trackedObject); // 校验传入的对象是否是追踪的同一个对象
return close() && trackedObject != null;
}
@Override
public boolean close() {
if (allLeaks.remove(this, LeakEntry.INSTANCE)) { // 从 allLeaks 中移除
clear(); // 清除弱引用
headUpdater.set(this, null); // 将 head 设置为 null,表示已关闭
return true;
}
return false; // 已经关闭过
}
首先会校验传入的
trackedObject是否与创建DefaultResourceLeak时追踪的是同一个对象。调用内部的
close()方法:从
allLeaks集合中移除当前的DefaultResourceLeak对象。清除对被追踪对象的弱引用。
将
head设置为null,表示该DefaultResourceLeak对象已关闭,不再进行泄漏报告。
如果
DefaultResourceLeak对象之前已经关闭过,则allLeaks.remove()会返回false。
5.1.5 toString
#toString() 方法用于生成内存泄漏报告的详细信息,包括最近的访问记录堆栈跟踪。
@Override
public String toString() {
Record oldHead = headUpdater.getAndSet(this, null); // 获取并置空 head,标记已生成报告
if (oldHead == null) {
return EMPTY_STRING; // 已经关闭
}
final int dropped = droppedRecordsUpdater.get(this);
int duped = 0;
int present = oldHead.pos + 1;
StringBuilder buf = new StringBuilder(present * 2048).append(NEWLINE);
buf.append("Recent access records: ").append(NEWLINE);
int i = 1;
Set<String> seen = new HashSet<String>(present);
for (; oldHead != Record.BOTTOM; oldHead = oldHead.next) {
String s = oldHead.toString(); // 获取 Record 的字符串表示
if (seen.add(s)) { // 记录是否重复
if (oldHead.next == Record.BOTTOM) {
buf.append("Created at:").append(NEWLINE).append(s);
} else {
buf.append('#').append(i++).append(':').append(NEWLINE).append(s);
}
} else {
duped++; // 记录重复次数
}
}
if (duped > 0) {
buf.append(": ").append(dropped).append(" leak records were discarded because they were duplicates").append(NEWLINE);
}
if (dropped > 0) {
buf.append(": ").append(dropped).append(" leak records were discarded because the leak record count is targeted to ").append(TARGET_RECORDS).append(". Use system property ").append(PROP_TARGET_RECORDS).append(" to increase the limit.").append(NEWLINE);
}
buf.setLength(buf.length() - NEWLINE.length());
return buf.toString();
}
首先通过
headUpdater.getAndSet(this, null)获取当前的head节点,并将其置为null。这是一个关键点,意味着一旦toString()被调用,后续将无法再获取到访问记录。在调试时需要注意 IDEA 可能会自动调用toString()方法。遍历
Record链表,将每个Record的字符串表示添加到StringBuilder中,记录是否重复出现相同的堆栈信息。特殊处理链表的第一个
Record(即创建时的Record),标记为 “Created at”。拼接重复记录的计数和由于超过
TARGET_RECORDS而被丢弃的记录计数。
6. LeakEntry
LeakEntry 是一个简单的内部静态类,用作 ResourceLeakDetector.allLeaks ConcurrentMap 的 value 值。
private static final class LeakEntry {
static final LeakEntry INSTANCE = new LeakEntry();
private static final int HASH = System.identityHashCode(INSTANCE);
private LeakEntry() { // 私有构造器,确保只能获取单例
}
@Override
public int hashCode() {
return HASH;
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
}
INSTANCE:LeakEntry的单例实例。HASH: 预先计算好的单例对象的 identity hash code。LeakEntry作为一个标记对象,其存在与否表示对应的DefaultResourceLeak对象是否正在被追踪。由于ConcurrentMap没有高效的ConcurrentSet实现,因此使用LeakEntry作为 value,只关心 key 的存在性。
7. Record
Record 是一个内部静态类,继承自 Throwable,用于记录资源被访问时的堆栈信息。每次调用 ResourceLeakTracker#touch(...) 方法时,都会创建一个新的 Record 对象。
private static final class Record extends Throwable {
private static final long serialVersionUID = 6065153674892850720L;
private static final Record BOTTOM = new Record(); // 链表的尾节点
private final String hintString;
private final Record next;
private final int pos;
// =========== 构造方法 ===========
Record(Record next, Object hint) {
hintString = hint instanceof ResourceLeakHint ? ((ResourceLeakHint) hint).toHintString() : hint.toString();
this.next = next;
this.pos = next.pos + 1;
}
Record(Record next) {
hintString = null;
this.next = next;
this.pos = next.pos + 1;
}
private Record() { // 用于创建尾节点 BOTTOM
hintString = null;
next = null;
pos = -1;
}
// =========== toString ===========
@Override
public String toString() {
StringBuilder buf = new StringBuilder(2048);
if (hintString != null) {
buf.append("\tHint: ").append(hintString).append(NEWLINE);
}
StackTraceElement[] array = getStackTrace();
out: for (int i = 3; i < array.length; i++) { // 跳过前三个栈帧(Record 构造方法和 record0 方法)
StackTraceElement element = array;
// 跳过忽略的方法
String[] exclusions = excludedMethods.get();
for (int k = 0; k < exclusions.length; k += 2) {
if (exclusions\\[k].equals(element.getClassName())
&& exclusions\[k + 1].equals(element.getMethodName())) {
continue out;
}
}
buf.append('\t');
buf.append(element.toString());
buf.append(NEWLINE);
}
return buf.toString();
}
}
hintString: 存储通过 touch(Object hint) 方法传入的额外调试信息。
next: 指向链表中的下一个 Record 对象,形成一个单向链表,记录了资源的访问历史。
pos: 记录当前 Record 在链表中的位置(深度)。
构造方法用于创建 Record 对象,并关联 hint 信息和下一个 Record。BOTTOM 是链表的末尾标记。
toString() 方法用于生成 Record 的字符串表示,包括 hint 信息和当前线程的堆栈跟踪。它会跳过 Record 自身构造方法和 record0 方法的栈帧,并会根据 ResourceLeakDetector.exclusions 排除配置的方法栈帧,使泄漏报告更清晰。
8. 配置参数与最佳实践
8.1 配置参数
Netty 提供了多个系统属性来配置内存泄漏检测的行为:
| 系统属性 | 默认值 | 说明 |
io.netty.leakDetection.level | SIMPLE | 内存泄漏检测级别,可选值:DISABLED, SIMPLE, ADVANCED, PARANOID |
io.netty.leakDetectionLevel | 同上 | 同上,兼容旧版本 |
io.netty.leakDetection.targetRecords | 4 | 每个 DefaultResourceLeak 记录的最大 Record 数量 |
io.netty.leakDetection.samplingInterval | 128 | 采样频率,值越大采样率越低 |
io.netty.leakDetection.acquireAndReleaseOnly | false | 是否只记录 acquire 和 release 操作,不记录其他操作 |
可以通过 JVM 参数设置这些属性,例如:
-Dio.netty.leakDetection.level=ADVANCED
-Dio.netty.leakDetection.targetRecords=8
8.2 性能影响
不同的内存泄漏检测级别对性能的影响不同:
DISABLED: 完全禁用泄漏检测,无性能开销,适用于生产环境中对性能要求极高的场景。
SIMPLE: 默认级别,采用采样策略,只报告是否存在泄漏,性能开销很小(约 1%),适用于大多数生产环境。
ADVANCED: 高级采样检测,报告泄漏对象最近的访问位置,性能开销中等(约 2-3%),适用于测试环境或需要定位泄漏位置的生产环境。
PARANOID: 检测所有对象,报告最近访问位置,性能开销很大(约 10-20%),仅适用于测试环境。
8.3 最佳实践
- 生产环境配置:
对于大多数生产环境,建议使用
SIMPLE级别,这是默认设置。如果系统对性能要求极高,可以考虑使用
DISABLED级别,但需要确保在测试环境中已经充分验证没有内存泄漏。
- 测试环境配置:
在测试环境中,建议使用
ADVANCED或PARANOID级别,以便及早发现和定位内存泄漏问题。对于压力测试或性能测试,可以考虑使用
SIMPLE级别,以减少检测对测试结果的影响。
- 调试技巧:
当发现内存泄漏时,可以临时将级别调整为
ADVANCED或PARANOID,以获取更详细的泄漏信息。使用
touch(Object hint)方法在关键位置添加自定义标记,帮助定位泄漏位置。增加
targetRecords的值,以记录更多的访问历史,有助于定位复杂的泄漏问题。
- 代码规范:
始终遵循
try-finally模式使用ByteBuf,确保在finally块中调用release()。使用
ReferenceCountUtil.release()方法释放资源,它会检查对象是否实现了ReferenceCounted接口。避免将
ByteBuf对象存储在长生命周期的容器中,除非明确增加其引用计数。
9. 内存泄漏检测工作原理总结
9.1 基本原理
Netty 的内存泄漏检测机制基于以下核心原理:
弱引用跟踪:使用
WeakReference和ReferenceQueue跟踪对象的回收情况。装饰器模式:通过
LeakAwareByteBuf装饰原始的ByteBuf对象,拦截引用计数相关操作。采样策略:使用采样策略减少检测开销,只对部分对象进行泄漏检测。
访问记录:记录对象的访问历史,帮助定位泄漏位置。
9.2 检测流程
创建
ByteBuf对象时,根据检测级别决定是否创建ResourceLeakTracker对象进行跟踪。如果需要跟踪,则创建
DefaultResourceLeak对象(继承自WeakReference),并将其添加到allLeaks集合中。当
ByteBuf对象被正确释放时,会调用ResourceLeakTracker.close()方法,从allLeaks集合中移除对应的DefaultResourceLeak对象。当
ByteBuf对象被 GC 回收时,对应的DefaultResourceLeak对象会被添加到ReferenceQueue中。在下一次创建
ResourceLeakTracker对象时,会检查ReferenceQueue中是否有待处理的对象。如果发现
DefaultResourceLeak对象仍然存在于allLeaks集合中,则表示在 GC 发生前资源没有被正确释放,即发生了内存泄漏。根据检测级别,生成不同详细程度的泄漏报告。
9.3 采样机制
为了减少检测开销,Netty 采用了采样策略:
SIMPLE和ADVANCED级别:以1/samplingInterval(默认 1/128)的概率对对象进行跟踪。PARANOID级别:对所有对象进行跟踪。DISABLED级别:不进行任何跟踪。
9.4 记录机制
SimpleLeakAwareByteBuf:只在对象释放时检查是否泄漏,不记录访问历史。AdvancedLeakAwareByteBuf:记录对象的访问历史,包括retain(),release(),touch()等操作,以及其他ByteBuf方法的调用。每个
DefaultResourceLeak对象最多记录TARGET_RECORDS(默认 4)个Record对象,超过时会随机丢弃部分记录。
10. 总结
Netty 的内存泄漏检测机制是一个精心设计的系统,它在性能和检测能力之间取得了良好的平衡。通过引用计数和内存泄漏检测,Netty 能够高效地管理内存资源,同时提供强大的调试能力,帮助开发者及早发现和解决内存泄漏问题。
在实际使用中,开发者应该根据不同的环境和需求,选择合适的检测级别,并遵循最佳实践,确保系统的稳定性和性能。
