1. 概述
在本文,我们来分享 Netty 的内存泄露检测的实现机制。考虑到胖友更好的理解本文,请先阅读江南白衣大大的 《Netty 之有效规避内存泄漏》 。
因为江南白衣大大在文章中,已经很清晰的讲解了概念与原理,笔者就不班门弄斧,直接上手,撸源码。
2. ReferenceCounted
FROM 《【Netty官方文档翻译】引用计数对象(reference counted objects)》
自从 Netty 4 开始,对象的生命周期由它们的引用计数( reference counts )管理,而不是由垃圾收集器( garbage collector )管理了。ByteBuf 是最值得注意的,它使用了引用计数来改进分配内存和释放内存的性能。
在 Netty 中,通过 io.netty.util.ReferenceCounted
接口,定义了引用计数相关的一系列操作。代码如下:
public interface ReferenceCounted { |
#refCnt()
、#retain(...)
、#release(...)
三种方法比较好理解,对引用指数的获取与增减。#touch(...)
方法,主动记录一个hint
给 ResourceLeakDetector ,方便我们在发现内存泄露有更多的信息进行排查。详细的,在下文 ResourceLeakDetector 相关的内容,具体来看。
ReferenceCounted 的直接子类 / 子接口有两个 :
io.netty.buffer.ByteBuf
。所以,所有 ByteBuf 实现类,都支持引用计数的操作。io.netty.util.AbstractReferenceCounted
,ReferenceCounted 的抽象实现类。它的子类实现类,主要是除了 ByteBuf 之外,需要引用计数的操作的类。例如:AbstractHttpData、DefaultFileRegion 等等。- AbstractReferenceCounted 不是本文的重点,就不多做介绍。
- AbstractReferenceCounted 的具体代码实现,在下文中,我们会看到和
io.netty.buffer.AbstractReferenceCountedByteBuf
基本差不多。
3. ByteBuf
ByteBuf 虽然继承了 ReferenceCounted 接口,但是并未实现相应的方法。那么真正实现与相关的类,如下图所示:类图
- 黄框
- AbstractReferenceCountedByteBuf ,实现引用计数的获取与增减的操作。
- 红框
- WrappedByteBuf ,实现对 ByteBuf 的装饰器实现类。
- WrappedCompositeByteBuf ,实现对 CompositeByteBuf 的装饰器实现类。
- 绿框
- SimpleLeakAwareByteBuf、SimpleLeakAwareCompositeByteBuf ,实现了
SIMPLE
级别的内存泄露检测。 - AdvancedLeakAwareByteBuf、AdvancedLeakAwareCompositeByteBuf ,实现了
ADVANCED
和PARANOID
级别的内存泄露检测。
- SimpleLeakAwareByteBuf、SimpleLeakAwareCompositeByteBuf ,实现了
- 蓝筐
- UnreleasableByteBuf ,用于阻止他人对装饰的 ByteBuf 的销毁,避免被错误销毁掉。
因为带 "Composite"
类的代码实现,和不带的类( 例如 WrappedCompositeByteBuf 和 WrappedByteBuf ),实现代码基本一致,所以本文只分享不带 "Composite"
的类。
3.1 创建 LeakAware ByteBuf 对象
在前面的文章中,我们已经提到,ByteBufAllocator 可用于创建 ByteBuf 对象。创建的过程中,它会调用 #toLeakAwareBuffer(...)
方法,将 ByteBuf 装饰成 LeakAware ( 可检测内存泄露 )的 ByteBuf 对象,代码如下:
// AbstractByteBufAllocator.java |
- 有两个
#toLeakAwareBuffer(...)
方法,分别对应带"Composite"
的 组合 ByteBuf 类,和不带Composite
普通 ByteBuf 类。因为这个不同,所以前者创建的是 SimpleLeakAwareCompositeByteBuf / AdvancedLeakAwareCompositeByteBuf 对象,后者创建的是 SimpleLeakAwareByteBuf / AdvancedLeakAwareByteBuf 对象。 - 当然,从总的逻辑来看,是一致的:
SIMPLE
级别,创建 SimpleLeakAwareByteBuf 或 SimpleLeakAwareCompositeByteBuf 对象。ADVANCED
和PARANOID
级别,创建 AdvancedLeakAwareByteBuf 或者 AdvancedLeakAwareCompositeByteBuf 对象。
- 是否需要创建 LeakAware ByteBuf 对象,有一个前提,调用
ResourceLeakDetector#track(ByteBuf)
方法,返回了 ResourceLeakTracker 对象。- 虽然说,
ADVANCED
和PARANOID
级别,都使用了 AdvancedLeakAwareByteBuf 或 AdvancedLeakAwareCompositeByteBuf 对象,但是它们的差异是:1)PARANOID
级别,一定返回 ResourceLeakTracker 对象;2)ADVANCED
级别,随机概率( 默认为1%
左右 )返回 ResourceLeakTracker 对象。 - 关于
ResourceLeakDetector#track(ByteBuf)
方法的实现,下文也会详细解析。
- 虽然说,
3.2 AbstractReferenceCountedByteBuf
io.netty.buffer.AbstractReferenceCountedByteBuf
,实现引用计数的获取与增减的操作。
3.2.1 构造方法
/** |
- 为什么
refCnt
不使用 AtomicInteger 呢?
计数器基于 AtomicIntegerFieldUpdater ,为什么不直接用 AtomicInteger ?因为 ByteBuf 对象很多,如果都把
int
包一层 AtomicInteger 花销较大,而AtomicIntegerFieldUpdater 只需要一个全局的静态变量。
3.2.2 refCnt
|
3.2.3 setRefCnt
#setRefCnt(int refCnt)
方法,直接修改 refCnt
。代码如下:
/** |
3.2.4 retain
|
3.2.5 release
|
当释放完成,即
refCnt
等于 0 时,调用#deallocate()
方法,进行真正的释放。这是个抽象方法,需要子类去实现。代码如下:/**
* Called once {@link #refCnt()} is equals 0.
*/
protected abstract void deallocate();- 在 《精尽 Netty 源码解析 —— Buffer 之 ByteBuf(二)核心子类》 中,可以看到各种 ByteBuf 对
#deallocate()
方法的实现。
- 在 《精尽 Netty 源码解析 —— Buffer 之 ByteBuf(二)核心子类》 中,可以看到各种 ByteBuf 对
3.2.6 touch
|
一脸懵逼?!实际 AbstractReferenceCountedByteBuf 并未实现 #touch(...)
方法。而是在 AdvancedLeakAwareByteBuf 中才实现。
3.3 SimpleLeakAwareByteBuf
io.netty.buffer.SimpleLeakAwareByteBuf
,继承 WrappedByteBuf 类,Simple
级别的 LeakAware ByteBuf 实现类。
3.3.1 构造方法
/** |
leak
属性,ResourceLeakTracker 对象。trackedByteBuf
属性,真正关联leak
的 ByteBuf 对象。- 对于构造方法
<1>
,wrapped
和trackedByteBuf
相同。 - 对于构造方法
<2>
,wrapped
和trackedByteBuf
一般不同。 - 有点难理解?继续往下看。
- 对于构造方法
3.3.2 slice
|
- 首先,调用父
#slice(...)
方法,获得 slice ByteBuf 对象。 之后,因为 slice ByteBuf 对象,并不是一个 LeakAware 的 ByteBuf 对象。所以调用
#newSharedLeakAwareByteBuf(ByteBuf wrapped)
方法,装饰成 LeakAware 的 ByteBuf 对象。代码如下:private SimpleLeakAwareByteBuf newSharedLeakAwareByteBuf(ByteBuf wrapped) {
return newLeakAwareByteBuf(wrapped, trackedByteBuf /** <1> **/, leak);
}
protected SimpleLeakAwareByteBuf newLeakAwareByteBuf(ByteBuf buf, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leakTracker) {
return new SimpleLeakAwareByteBuf(buf, trackedByteBuf /** <1> **/, leakTracker);
}- 从
<1>
处,我们可以看到,trackedByteBuf
代表的是原始的 ByteBuf 对象,它是跟leak
真正进行关联的。而wrapped
则不是。
- 从
在 SimpleLeakAwareByteBuf 中,还有如下方法,和 #slice(...)
方法是类似的,在调用完父对应的方法后,再调用 #newSharedLeakAwareByteBuf(ByteBuf wrapped)
方法,装饰成 LeakAware 的 ByteBuf 对象。整理如下:
|
3.3.3 retainedSlice
|
- 首先,调用父
#retainedSlice(...)
方法,获得 slice ByteBuf 对象,引用计数加 1。 之后,因为 slice ByteBuf 对象,并不是一个 LeakAware 的 ByteBuf 对象。所以调用
#unwrappedDerived(ByteBuf wrapped)
方法,装饰成 LeakAware 的 ByteBuf 对象。代码如下:// TODO 芋艿,看不懂 1017
private ByteBuf unwrappedDerived(ByteBuf derived) {
// We only need to unwrap SwappedByteBuf implementations as these will be the only ones that may end up in
// the AbstractLeakAwareByteBuf implementations beside slices / duplicates and "real" buffers.
ByteBuf unwrappedDerived = unwrapSwapped(derived);
if (unwrappedDerived instanceof AbstractPooledDerivedByteBuf) {
// Update the parent to point to this buffer so we correctly close the ResourceLeakTracker.
((AbstractPooledDerivedByteBuf) unwrappedDerived).parent(this);
ResourceLeakTracker<ByteBuf> newLeak = AbstractByteBuf.leakDetector.track(derived);
if (newLeak == null) {
// No leak detection, just return the derived buffer.
return derived;
}
return newLeakAwareByteBuf(derived, newLeak);
}
return newSharedLeakAwareByteBuf(derived);
}
"deprecation") (
private static ByteBuf unwrapSwapped(ByteBuf buf) {
if (buf instanceof SwappedByteBuf) {
do {
buf = buf.unwrap();
} while (buf instanceof SwappedByteBuf);
return buf;
}
return buf;
}
private SimpleLeakAwareByteBuf newLeakAwareByteBuf(ByteBuf wrapped, ResourceLeakTracker<ByteBuf> leakTracker) {
return newLeakAwareByteBuf(wrapped, wrapped, leakTracker);
}- TODO 1017
在 SimpleLeakAwareByteBuf 中,还有如下方法,和 #retainedSlice(...)
方法是类似的,在调用完父对应的方法后,再调用 #unwrappedDerived(ByteBuf derived)
方法,装饰成 LeakAware 的 ByteBuf 对象。整理如下:
|
3.3.4 release
|
在调用父
#release(...)
方法,释放完成后,会调用#closeLeak()
方法,关闭 ResourceLeakTracker 。代码如下:private void closeLeak() {
// Close the ResourceLeakTracker with the tracked ByteBuf as argument. This must be the same that was used when
// calling DefaultResourceLeak.track(...).
boolean closed = leak.close(trackedByteBuf);
assert closed;
}
* 进一步的详细解析,可以看看 [「5.1.5 close」](#) 。
3.3.5 touch
|
又一脸懵逼?!实际 SimpleLeakAwareByteBuf 也并未实现 #touch(...)
方法。而是在 AdvancedLeakAwareByteBuf 中才实现。
3.4 AdvancedLeakAwareByteBuf
io.netty.buffer.AdvancedLeakAwareByteBuf
,继承 SimpleLeakAwareByteBuf 类,ADVANCED
和 PARANOID
级别的 LeakAware ByteBuf 实现类。
3.4.1 构造方法
AdvancedLeakAwareByteBuf(ByteBuf buf, ResourceLeakTracker<ByteBuf> leak) { |
就是调用父构造方法,没啥特点。
3.4.2 retain
|
- 会调用
ResourceLeakTracer#record()
方法,记录信息。
3.4.3 release
|
- 会调用
ResourceLeakTracer#record()
方法,记录信息。
3.4.4 touch
|
- 会调用
ResourceLeakTracer#record(...)
方法,记录信息。 - 😈
#touch(...)
方法,终于实现了,哈哈哈。
3.4.5 recordLeakNonRefCountingOperation
#recordLeakNonRefCountingOperation(ResourceLeakTracker<ByteBuf> leak)
静态方法,除了引用计数操作相关( 即 #retain(...)
/#release(...)
/#touch(...)
方法 )方法外,是否要调用记录信息。代码如下:
private static final String PROP_ACQUIRE_AND_RELEASE_ONLY = "io.netty.leakDetection.acquireAndReleaseOnly"; |
- 负负得正,所以会调用
ResourceLeakTracer#record(...)
方法,记录信息。 也就是说,ByteBuf 的所有方法,都会记录信息。例如:
public ByteBuf order(ByteOrder endianness) {
recordLeakNonRefCountingOperation(leak);
return super.order(endianness);
}
public int readIntLE() {
recordLeakNonRefCountingOperation(leak);
return super.readIntLE();
}- 方法比较多,就不一一列举了。
3.4.6 newLeakAwareByteBuf
#newLeakAwareByteBuf(ByteBuf buf, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leakTracker)
方法,覆写父类方法,将原先装饰成 SimpleLeakAwareByteBuf 改成 AdvancedLeakAwareByteBuf 对象。代码如下:
|
3.5 UnreleasableByteBuf
io.netty.buffer.UnreleasableByteBuf
,继承 WrappedByteBuf 类,用于阻止他人对装饰的 ByteBuf 的销毁,避免被错误销毁掉。
它的实现方法比较简单,主要是两大点:
引用计数操作相关( 即
#retain(...)
/#release(...)
/#touch(...)
方法 )方法,不进行调用。代码如下:
public ByteBuf retain(int increment) {
return this;
}
public ByteBuf retain() {
return this;
}
public ByteBuf touch() {
return this;
}
public ByteBuf touch(Object hint) {
return this;
}
public boolean release() {
return false;
}
public boolean release(int decrement) {
return false;
}
拷贝操作相关方法,都会在包一层 UnreleasableByteBuf 对象。例如:
public ByteBuf slice() {
return new UnreleasableByteBuf(buf.slice());
}
4. ResourceLeakDetector
io.netty.util.ResourceLeakDetector
,内存泄露检测器。
老艿艿:Resource 翻译成“资源”更合理。考虑到标题叫做《内存泄露检测》,包括互联网其他作者在关于这块内容的命名,也是叫做“内存泄露检测”。所以,在下文,Resource 笔者还是继续翻译成“资源”。
ResourceLeakDetector 为了检测内存是否泄漏,使用了 WeakReference( 弱引用 )和 ReferenceQueue( 引用队列 ),过程如下:
- 根据检测级别和采样率的设置,在需要时为需要检测的 ByteBuf 创建WeakReference 引用。
- 当 JVM 回收掉 ByteBuf 对象时,JVM 会将 WeakReference 放入ReferenceQueue 队列中。
- 通过对 ReferenceQueue 中 WeakReference 的检查,判断在 GC 前是否有释放ByteBuf 的资源,就可以知道是否有资源释放。
😈 看不太懂?继续往下看代码,在回过头来理解理解。
4.1 静态属性
private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel"; |
level
静态属性,内存泄露等级。😈 不是说好了,静态变量要统一大写么。- 默认级别为
DEFAULT_LEVEL = Level.SIMPLE
。 在 Level 中,枚举了四个级别。
- 禁用(DISABLED) - 完全禁止泄露检测,省点消耗。
- 简单(SIMPLE) - 默认等级,告诉我们取样的1%的ByteBuf是否发生了泄露,但总共一次只打印一次,看不到就没有了。
- 高级(ADVANCED) - 告诉我们取样的1%的ByteBuf发生泄露的地方。每种类型的泄漏(创建的地方与访问路径一致)只打印一次。对性能有影响。
- 偏执(PARANOID) - 跟高级选项类似,但此选项检测所有ByteBuf,而不仅仅是取样的那1%。对性能有绝大的影响。
- 看着有点懵逼?下面继续看代码。
- 在【第 2 至 23 行】的代码进行初始化。
- 默认级别为
TARGET_RECORDS
静态属性,每个 DefaultResourceLeak 记录的 Record 数量。- 默认大小为
DEFAULT_TARGET_RECORDS = 4
。 - 在【第 26 行】的代码进行初始化。
- 默认大小为
DEFAULT_SAMPLING_INTERVAL
静态属性,默认采集频率,128 。
4.2 构造方法
/** |
allLeaks
属性,DefaultResourceLeak 集合。因为 Java 没有自带的 ConcurrentSet ,所以只好使用使用 ConcurrentMap 。也就是说,value 属性实际没有任何用途。- 关于 LeakEntry ,可以看下 「6. LeakEntry」 。
refQueue
属性,就是我们提到的引用队列( ReferenceQueue 队列 )。reportedLeaks
属性,已汇报的内存泄露的资源类型的集合。resourceType
属性,资源类型,使用资源类的类名简写,见<1>
处。samplingInterval
属性,采集频率。
在 AbstractByteBuf 类中,我们可以看到创建了所有 ByteBuf 对象统一使用的 ResourceLeakDetector 对象。代码如下:
static final ResourceLeakDetector<ByteBuf> leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class); |
- ResourceLeakDetector 的创建,通过
io.netty.util.ResourceLeakDetectorFactory
,基于工厂模式的方式来创建。- 关于 ResourceLeakDetectorFactory 的代码比较简单,笔者就不赘述了。
- 有一点要注意的是,可以通过
"io.netty.customResourceLeakDetector"
来自定义 ResourceLeakDetector 的实现类。当然,绝大多数场景是完全不需要的。
4.3 track
#track(...)
方法,给指定资源( 例如 ByteBuf 对象 )创建一个检测它是否泄漏的 ResourceLeakTracker 对象。代码如下:
1: public final ResourceLeakTracker<T> track(T obj) { |
- 第 8 至 11 行:
DISABLED
级别时,不创建,直接返回null
。 - 第 13 至 23 行:
SIMPLE
和ADVANCED
级别时,随机,概率为1 / samplingInterval
,创建 DefaultResourceLeak 对象。默认情况下samplingInterval = 128
,约等于1%
,这也是就为什么说“告诉我们取样的 1% 的ByteBuf发生泄露的地方”。 - 第 27 至 29 行:
PARANOID
级别时,一定创建 DefaultResourceLeak 对象。这也是为什么说“对性能有绝大的影响”。 - 第 18 至 27 行:笔者原本以为,ResourceLeakDetector 会有一个定时任务,不断检测是否有内存泄露。从这里的代码来看,它是在每次一次创建 DefaultResourceLeak 对象时,调用
#reportLeak()
方法,汇报内存是否泄漏。详细解析,见 「4.4 reportLeak」 。
4.4 reportLeak
#reportLeak()
方法,检测是否有内存泄露。若有,则进行汇报。代码如下:
1: private void reportLeak() { |
- 第 2 至 7 行:如果不允许打印错误日志,则无法汇报,因此调用
#clearRefQueue()
方法,清理队列,并直接结束。详细解析,见 「4.5 clearRefQueue」 。 - 第 9 至 16 行:循环引用队列
refQueue
,直到为空。 - 第 18 至 21 行:调用
DefaultResourceLeak#dispose()
方法,清理,并返回是否内存泄露。如果未泄露,就直接continue
。详细解析,见 「5.1.3 dispose」 。 - 第 24 行:调用
DefaultResourceLeak#toString()
方法,获得 Record 日志。详细解析,见 「5.1 DefaultResourceLeak」 。 第 25 至 32 行:相同 Record 日志内容( 即“创建的地方与访问路径一致” ),只汇报一次。 代码如下:
/**
* This method is called when a traced leak is detected. It can be overridden for tracking how many times leaks
* have been detected.
*/
protected void reportTracedLeak(String resourceType, String records) {
logger.error(
"LEAK: {}.release() was not called before it's garbage-collected. " +
"See http://netty.io/wiki/reference-counted-objects.html for more information.{}",
resourceType, records);
}
/**
* This method is called when an untraced leak is detected. It can be overridden for tracking how many times leaks
* have been detected.
*/
protected void reportUntracedLeak(String resourceType) {
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() " +
"See http://netty.io/wiki/reference-counted-objects.html for more information.",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
}
😈 这块逻辑的信息量,可能有点大,胖友可以看完 「5. ResourceLeakTracker」 ,再回过头理解下。
4.5 clearRefQueue
#clearRefQueue()
方法,清理队列。代码如下:
private void clearRefQueue() { |
- 实际上,就是
#reportLeak()
方法的不汇报内存泄露的版本。
5. ResourceLeakTracker
io.netty.util.ResourceLeakTracker
,内存泄露追踪器接口。从 「4.3 track」 中,我们已经看到,每个资源( 例如:ByteBuf 对象 ),会创建一个追踪它是否内存泄露的 ResourceLeakTracker 对象。
接口方法定义如下:
public interface ResourceLeakTracker<T> { |
#record(...)
方法,出于调试目的,用一个额外的任意的( arbitrary )信息记录这个对象的当前访问地址。如果这个对象被检测到泄露了, 这个操作记录的信息将通过ResourceLeakDetector 提供。实际上,就是ReferenceCounted#touch(...)
方法,会调用#record(...)
方法。#close(T trackedObject)
方法,关闭 ResourceLeakTracker 。如果资源( 例如:ByteBuf 对象 )被正确释放,则会调用#close(T trackedObject)
方法,关闭 ResourceLeakTracker ,从而结束追踪。这样,在ResourceLeakDetector#reportLeak()
方法,就不会提示该资源泄露。
4.6 addExclusions
#addExclusions(Class clz, String ... methodNames)
方法,添加忽略方法的集合。代码如下:
/** |
- 代码比较简单,胖友自己理解。
- 具体的用途,可参见 「7. Record」 的
#toString()
方法。 目前调用该静态方法的有如下几处:
// AbstractByteBufAllocator.java
static {
ResourceLeakDetector.addExclusions(AbstractByteBufAllocator.class, "toLeakAwareBuffer");
}
// AdvancedLeakAwareByteBuf.java
static {
ResourceLeakDetector.addExclusions(AdvancedLeakAwareByteBuf.class, "touch", "recordLeakNonRefCountingOperation");
}
// ReferenceCountUtil.java
static {
ResourceLeakDetector.addExclusions(ReferenceCountUtil.class, "touch");
}
5.1 DefaultResourceLeak
DefaultResourceLeak ,继承 java.lang.ref.WeakReference
类,实现 ResourceLeakTracker 接口,默认 ResourceLeakTracker 实现类。同时,它是 ResourceLeakDetector 内部静态类。即:
// ... 简化无关代码 |
那么为什么要继承 java.lang.ref.WeakReference
类呢?在 「5.1.1 构造方法」 见分晓。
5.1.1 构造方法
/** |
head
属性,Record 链的头节点。- 为什么说它是链呢?详细解析,胖友可以先跳到 「7. Record」 。
- 实际上,
head
是尾节点,即最后( 新 )的一条 Record 记录。详细解析,见 「5.1.2 record」 。 - 在【第 16 行】代码,会默认创建尾节点
Record.BOTTOM
。
droppedRecords
属性,丢弃的 Record 计数。详细解析,见 「5.1.2 record」 。allLeaks
属性,DefaultResourceLeak 集合。来自ResourceLeakDetector.allLeaks
属性。- 在【第 14 行】代码,会将自己添加到
allLeaks
中。
- 在【第 14 行】代码,会将自己添加到
trackedHash
属性,hash 值。保证在#close(T trackedObject)
方法,传入的对象,就是referent
属性,即就是 DefaultResourceLeak 指向的资源( 例如:ByteBuf 对象 )。详细解析,见 「5.1.4 close」 。- 在【第 10 至 13 行】代码,计算并初始化
trackedHash
属性。
- 在【第 10 至 13 行】代码,计算并初始化
【重要】在
<1>
处,会将referent
( 资源,例如:ByteBuf 对象 )和refQueue
( 引用队列 )传入父 WeakReference 构造方法。FROM 《译文:理解Java中的弱引用》
引用队列(Reference Queue)
一旦弱引用对象开始返回null,该弱引用指向的对象就被标记成了垃圾。而这个弱引用对象(非其指向的对象)就没有什么用了。通常这时候需要进行一些清理工作。比如WeakHashMap会在这时候移除没用的条目来避免保存无限制增长的没有意义的弱引用。
引用队列可以很容易地实现跟踪不需要的引用。当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾的时候,这个引用对象会自动地加入到引用队列里面。接下来,你就可以在固定的周期,处理传入的引用队列,比如做一些清理工作来处理这些没有用的引用对象。
- 也就是说,
referent
被标记为垃圾的时候,它对应的 WeakReference 对象会被添加到refQueue
队列中。在此处,即将 DefaultResourceLeak 添加到referent
队列中。 - 那又咋样呢?假设
referent
为 ByteBuf 对象。如果它被正确的释放,即调用了 「3.3.4 release」 方法,从而调用了AbstractReferenceCountedByteBuf#closeLeak()
方法,最终调用到ResourceLeakTracker#close(trackedByteBuf)
方法,那么该 ByteBuf 对象对应的 ResourceLeakTracker 对象,将从ResourceLeakDetector.allLeaks
中移除。 - 那这又意味着什么呢? 在
ResourceLeakDetector#reportLeak()
方法中,即使从refQueue
队列中,获取到该 ByteBuf 对象对应 ResourceLeakTracker 对象,因为在ResourceLeakDetector.allLeaks
中移除了,所以在ResourceLeakDetector#reportLeak()
方法的【第 19 行】代码!ref.dispose() = true
,直接continue
。 - 😈 比较绕,胖友再好好理解下。胖友可以在思考下,如果 ByteBuf 对象,没有被正确的释放,是怎么样一个流程。
- 也就是说,
5.1.2 record
#record(...)
方法,创建 Record 对象,添加到 head
链中。代码如下:
|
- 第 9 至 13 行:通过
headUpdater
获得head
属性,若为null
时,说明 DefaultResourceLeak 已经关闭。为什么呢?详细可见 「5.1.4 close」 和 5.1.5 toString 。 - 第 14 至 23 行:当当前 DefaultResourceLeak 对象所拥有的 Record 数量超过
TARGET_RECORDS
时,随机丢弃当前head
节点的数据。也就是说,尽量保留老的 Record 节点。这是为什么呢?越是老( 开始 )的 Record 节点,越有利于排查问题。另外,随机丢弃的的概率,按照1 - (1 / 2^n)
几率,越来越大。 - 第 25 行:创建新 Record 对象,作为头节点,指向原头节点。这也是为什么说,“实际上,head 是尾节点,即最后( 新 )的一条 Record”。
- 第 26 行:通过 CAS 的方式,修改新创建的 Record 对象为头节点。
- 第 27 至 30 行:若丢弃,增加
droppedRecordsUpdater
计数。
5.1.3 dispose
#dispose()
方法, 清理,并返回是否内存泄露。代码如下:
// 清理,并返回是否内存泄露 |
5.1.4 close
#close(T trackedObject)
方法,关闭 DefaultResourceLeak 对象。代码如下:
1: |
- 第 5 行:校验一致性。
第 12 行:调用
#close()
方法,关闭 DefaultResourceLeak 对象。代码如下:
public boolean close() {
// 移除出 allLeaks
// Use the ConcurrentMap remove method, which avoids allocating an iterator.
if (allLeaks.remove(this, LeakEntry.INSTANCE)) {
// 清理 referent 的引用
// Call clear so the reference is not even enqueued.
clear();
// 置空 head
headUpdater.set(this, null);
return true; // 返回成功
}
return false; // 返回失败
}- 关闭时,会将 DefaultResourceLeak 对象,从
allLeaks
中移除。
- 关闭时,会将 DefaultResourceLeak 对象,从
5.1.5 toString
当 DefaultResourceLeak 追踪到内存泄露,会在 ResourceLeakDetector#reportLeak()
方法中,调用 DefaultResourceLeak#toString()
方法,拼接提示信息。代码如下:
|
- 代码比较简单,胖友自己看注释。
<1>
处,真的是个神坑。如果胖友在 IDEA 调试时,因为默认会调用对应的#toString()
方法,会导致head
属性被错误的重置为null
值。wtf!!!笔者在这里卡了好久好久。
6. LeakEntry
LeakEntry ,用于 ResourceLeakDetector.allLeaks
属性的 value 值。代码如下:
private static final class LeakEntry { |
😈 没有什么功能逻辑。
7. Record
Record ,记录。每次调用 ResourceLeakTracker#touch(...)
方法后,会产生响应的 Record 对象。代码如下:
private static final class Record extends Throwable { |
- 通过
next
属性,我们可以得知,Record 是链式结构。 <1>
处,如果传入的hint
类型为 ResourceLeakHint 类型,会调用对应的#toHintString()
方法,拼接更友好的字符串提示信息。<2>
处,如果调用栈的方法在ResourceLeakDetector.exclusions
属性中,进行忽略。
8. ResourceLeakHint
io.netty.util.ResourceLeakHint
,接口,提供人类可读( 易懂 )的提示信息,使用在 ResourceLeakDetector 中。代码如下:
/** |
目前它的实现类是 AbstractChannelHandlerContext 。对应的实现方法如下:
/** |
666. 彩蛋
比想象中长很多的文章,也比想象中花费了更多时间的文章。主要是 xxx 的 「5.1.5 toString」 中卡了好久啊!!!!
推荐阅读文章:
上述两篇文章,因为分析的 Netty 不是最新版本,所以代码会有一些差异,例如 maxActive
已经被去除。