Repository navigation
netty leak_detector
wangkaiwu edited this page Feb 27, 2023
·
1 revision
在java程序启动时,加入参数 -Dio.netty.leakDetection.level=PARANOID 可以查出 ByteBuf 泄露的位置。从这个参数聊起,看下如何生效的
public class ResourceLeakDetector<T> {
private static final String PROP_LEVEL = "io.netty.leakDetection.level";
private static final String PROP_TARGET_RECORDS = "io.netty.leakDetection.targetRecords";
private static final int DEFAULT_TARGET_RECORDS = 4;
private static final String PROP_SAMPLING_INTERVAL = "io.netty.leakDetection.samplingInterval";
private static final int DEFAULT_SAMPLING_INTERVAL = 128;
private static final int TARGET_RECORDS;
static final int SAMPLING_INTERVAL;
private static Level level;
static {
String levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr);
Level level = Level.parseLevel(levelStr);
// 默认值是 4
TARGET_RECORDS = SystemPropertyUtil.getInt(PROP_TARGET_RECORDS, DEFAULT_TARGET_RECORDS);
// 默认值 128
SAMPLING_INTERVAL = SystemPropertyUtil.getInt(PROP_SAMPLING_INTERVAL, DEFAULT_SAMPLING_INTERVAL);
ResourceLeakDetector.level = level;
}
}public enum Level {
DISABLED,
SIMPLE,
ADVANCED,
PARANOID;
}通过调用 track0 方法可以打印泄露位置
查找泄露源
public class ResourceLeakDetector<T> {
private final Set<DefaultResourceLeak<?>> allLeaks =
Collections.newSetFromMap(new ConcurrentHashMap<DefaultResourceLeak<?>, Boolean>());
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
private DefaultResourceLeak track0(T obj) {
Level level = ResourceLeakDetector.level;
if (level == Level.DISABLED) {
return null;
}
if (level.ordinal() < Level.PARANOID.ordinal()) {
// 默认1/128的概率随机
if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {
reportLeak();
return new DefaultResourceLeak(obj, refQueue, allLeaks);
}
return null;
}
reportLeak();
return new DefaultResourceLeak(obj, refQueue, allLeaks);
}
}public class ResourceLeakDetector<T> {
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
private void reportLeak() {
for (; ; ) {
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
if (!ref.dispose()) {
continue;
}
String records = ref.toString();
if (reportedLeaks.add(records)) {
if (records.isEmpty()) {
reportUntracedLeak(resourceType);
} else {
reportTracedLeak(resourceType, records);
}
}
}
}
}- reportUntracedLeak,无法找到泄露源
public class ResourceLeakDetector<T> {
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 https://netty.io/wiki/reference-counted-objects.html for more information.",
resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
}
}- reportTracedLeak,找到泄露源,并打印泄露位置
public class ResourceLeakDetector<T> {
protected void reportTracedLeak(String resourceType, String records) {
logger.error(
"LEAK: {}.release() was not called before it's garbage-collected. " +
"See https://netty.io/wiki/reference-counted-objects.html for more information.{}",
resourceType, records);
}
}由于追踪 ByteBuf 时,都会生成一个 DefaultResourceLeak 对象,构造的时候会传入 refQueue 和 allLeaks。
public class ResourceLeakDetector<T> {
private static final class DefaultResourceLeak<T>
extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
private final Set<DefaultResourceLeak<?>> allLeaks;
DefaultResourceLeak(
Object referent,
ReferenceQueue<Object> refQueue,
Set<DefaultResourceLeak<?>> allLeaks) {
super(referent, refQueue);
// .......
allLeaks.add(this);
this.allLeaks = allLeaks;
}
}
}根据弱引用的规则,当引用对象被回收的时候,会将引用对象放入 refQueue 中。
public class WeakReference<T> extends Reference<T> {
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}通过下面的对象可知,每个 ByteBuf 其实都包装了一个强引用的 trackedByteBuf 和一个弱引用的 leak。 一旦强引用对象GCRoot 不可达,则 gc 触发后会同时清理弱引用的 leak 对象,同时置入 refQueue。
class SimpleLeakAwareByteBuf extends WrappedByteBuf {
private final ByteBuf trackedByteBuf;
final ResourceLeakTracker<ByteBuf> leak;
}所以当 ByteBuf 应该被 GC 时,会置入 refQueue。因此对象是否泄露,重点要看 ref.dispose() 方法。
其实这里只是判断一下对象是否调用过 track,所以只要进入了 refQueue,则对象一定泄露了。 而我们知道,一个对象一旦失去强引用,就会被 GC 回收。 然而对象从池中来,调用release后会回到池中,也就是说正常使用并回收并不会失去强引用。 而如果一个对象没有正常放回池中,而强引用又丢失了,那么肯定泄露了,泄露者就是最后一次 touch 的位置。
public class ResourceLeakDetector<T> {
private static final class DefaultResourceLeak<T>
extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
private final Set<DefaultResourceLeak<?>> allLeaks;
boolean dispose() {
clear();
return allLeaks.remove(this);
}
}
}public class ResourceLeakDetector<T> {
private static final class DefaultResourceLeak<T>
extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
@Override
public String toString() {
Record oldHead = headUpdater.getAndSet(this, null);
if (oldHead == null) {
return EMPTY_STRING;
}
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();
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++;
}
}
//...........
return buf.toString();
}
}
}