2013-01-11 22:47:54 +09:00
|
|
|
/*
|
|
|
|
* Copyright 2013 The Netty Project
|
|
|
|
*
|
|
|
|
* The Netty Project licenses this file to you under the Apache License,
|
|
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
|
|
* with the License. You may obtain a copy of the License at:
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
|
|
* License for the specific language governing permissions and limitations
|
|
|
|
* under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package io.netty.util;
|
|
|
|
|
2015-10-28 11:46:28 +01:00
|
|
|
import io.netty.util.internal.MathUtil;
|
2013-02-26 15:54:51 -08:00
|
|
|
import io.netty.util.internal.PlatformDependent;
|
2013-01-11 22:47:54 +09:00
|
|
|
import io.netty.util.internal.SystemPropertyUtil;
|
2013-02-26 14:54:25 -08:00
|
|
|
import io.netty.util.internal.logging.InternalLogger;
|
|
|
|
import io.netty.util.internal.logging.InternalLoggerFactory;
|
2013-01-11 22:47:54 +09:00
|
|
|
|
2013-02-09 12:14:52 +09:00
|
|
|
import java.lang.ref.PhantomReference;
|
2013-01-11 22:47:54 +09:00
|
|
|
import java.lang.ref.ReferenceQueue;
|
2013-12-04 19:03:32 +09:00
|
|
|
import java.util.ArrayDeque;
|
|
|
|
import java.util.Deque;
|
|
|
|
import java.util.EnumSet;
|
2013-01-11 22:47:54 +09:00
|
|
|
import java.util.concurrent.ConcurrentMap;
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
2016-02-10 09:42:58 -08:00
|
|
|
import static io.netty.util.internal.StringUtil.EMPTY_STRING;
|
|
|
|
import static io.netty.util.internal.StringUtil.NEWLINE;
|
|
|
|
import static io.netty.util.internal.StringUtil.simpleClassName;
|
2013-12-04 19:03:32 +09:00
|
|
|
|
2016-06-15 00:19:15 -07:00
|
|
|
public class ResourceLeakDetector<T> {
|
2013-01-11 22:47:54 +09:00
|
|
|
|
2015-08-28 15:05:52 -07:00
|
|
|
private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel";
|
|
|
|
private static final String PROP_LEVEL = "io.netty.leakDetection.level";
|
2013-12-04 19:03:32 +09:00
|
|
|
private static final Level DEFAULT_LEVEL = Level.SIMPLE;
|
|
|
|
|
2015-08-28 15:05:52 -07:00
|
|
|
private static final String PROP_MAX_RECORDS = "io.netty.leakDetection.maxRecords";
|
|
|
|
private static final int DEFAULT_MAX_RECORDS = 4;
|
|
|
|
private static final int MAX_RECORDS;
|
|
|
|
|
2013-12-04 19:03:32 +09:00
|
|
|
/**
|
|
|
|
* Represents the level of resource leak detection.
|
|
|
|
*/
|
|
|
|
public enum Level {
|
|
|
|
/**
|
|
|
|
* Disables resource leak detection.
|
|
|
|
*/
|
|
|
|
DISABLED,
|
|
|
|
/**
|
|
|
|
* Enables simplistic sampling resource leak detection which reports there is a leak or not,
|
|
|
|
* at the cost of small overhead (default).
|
|
|
|
*/
|
|
|
|
SIMPLE,
|
|
|
|
/**
|
|
|
|
* Enables advanced sampling resource leak detection which reports where the leaked object was accessed
|
|
|
|
* recently at the cost of high overhead.
|
|
|
|
*/
|
|
|
|
ADVANCED,
|
|
|
|
/**
|
|
|
|
* Enables paranoid resource leak detection which reports where the leaked object was accessed recently,
|
|
|
|
* at the cost of the highest possible overhead (for testing purposes only).
|
|
|
|
*/
|
|
|
|
PARANOID
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Level level;
|
2013-06-10 19:13:06 +09:00
|
|
|
|
2013-01-11 22:47:54 +09:00
|
|
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(ResourceLeakDetector.class);
|
|
|
|
|
|
|
|
static {
|
2013-12-05 01:39:48 +09:00
|
|
|
final boolean disabled;
|
|
|
|
if (SystemPropertyUtil.get("io.netty.noResourceLeakDetection") != null) {
|
|
|
|
disabled = SystemPropertyUtil.getBoolean("io.netty.noResourceLeakDetection", false);
|
|
|
|
logger.debug("-Dio.netty.noResourceLeakDetection: {}", disabled);
|
|
|
|
logger.warn(
|
|
|
|
"-Dio.netty.noResourceLeakDetection is deprecated. Use '-D{}={}' instead.",
|
|
|
|
PROP_LEVEL, DEFAULT_LEVEL.name().toLowerCase());
|
|
|
|
} else {
|
|
|
|
disabled = false;
|
|
|
|
}
|
2013-12-05 01:02:38 +09:00
|
|
|
|
|
|
|
Level defaultLevel = disabled? Level.DISABLED : DEFAULT_LEVEL;
|
2015-08-28 15:05:52 -07:00
|
|
|
|
|
|
|
// First read old property name
|
|
|
|
String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name()).trim().toUpperCase();
|
|
|
|
|
|
|
|
// If new property name is present, use it
|
|
|
|
levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr).trim().toUpperCase();
|
2013-12-04 19:03:32 +09:00
|
|
|
Level level = DEFAULT_LEVEL;
|
|
|
|
for (Level l: EnumSet.allOf(Level.class)) {
|
|
|
|
if (levelStr.equals(l.name()) || levelStr.equals(String.valueOf(l.ordinal()))) {
|
|
|
|
level = l;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-28 15:05:52 -07:00
|
|
|
MAX_RECORDS = SystemPropertyUtil.getInt(PROP_MAX_RECORDS, DEFAULT_MAX_RECORDS);
|
|
|
|
|
2013-12-04 19:03:32 +09:00
|
|
|
ResourceLeakDetector.level = level;
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
logger.debug("-D{}: {}", PROP_LEVEL, level.name().toLowerCase());
|
2015-08-28 15:05:52 -07:00
|
|
|
logger.debug("-D{}: {}", PROP_MAX_RECORDS, MAX_RECORDS);
|
2013-12-04 19:03:32 +09:00
|
|
|
}
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
2013-04-03 11:44:30 +09:00
|
|
|
|
2015-10-28 11:46:28 +01:00
|
|
|
// Should be power of two.
|
2016-06-28 21:20:40 +02:00
|
|
|
static final int DEFAULT_SAMPLING_INTERVAL = 128;
|
2013-01-11 22:47:54 +09:00
|
|
|
|
2013-12-05 01:02:38 +09:00
|
|
|
/**
|
2013-12-05 01:39:48 +09:00
|
|
|
* @deprecated Use {@link #setLevel(Level)} instead.
|
2013-12-05 01:02:38 +09:00
|
|
|
*/
|
|
|
|
@Deprecated
|
|
|
|
public static void setEnabled(boolean enabled) {
|
|
|
|
setLevel(enabled? Level.SIMPLE : Level.DISABLED);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns {@code true} if resource leak detection is enabled.
|
|
|
|
*/
|
|
|
|
public static boolean isEnabled() {
|
|
|
|
return getLevel().ordinal() > Level.DISABLED.ordinal();
|
|
|
|
}
|
|
|
|
|
2013-07-22 20:55:49 +02:00
|
|
|
/**
|
2013-12-04 19:03:32 +09:00
|
|
|
* Sets the resource leak detection level.
|
2013-07-22 20:55:49 +02:00
|
|
|
*/
|
2013-12-04 19:03:32 +09:00
|
|
|
public static void setLevel(Level level) {
|
|
|
|
if (level == null) {
|
|
|
|
throw new NullPointerException("level");
|
|
|
|
}
|
|
|
|
ResourceLeakDetector.level = level;
|
2013-07-22 20:55:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2013-12-04 19:03:32 +09:00
|
|
|
* Returns the current resource leak detection level.
|
2013-07-22 20:55:49 +02:00
|
|
|
*/
|
2013-12-04 19:03:32 +09:00
|
|
|
public static Level getLevel() {
|
|
|
|
return level;
|
2013-07-22 20:55:49 +02:00
|
|
|
}
|
|
|
|
|
2013-01-11 22:47:54 +09:00
|
|
|
/** the linked list of active resources */
|
|
|
|
private final DefaultResourceLeak head = new DefaultResourceLeak(null);
|
|
|
|
private final DefaultResourceLeak tail = new DefaultResourceLeak(null);
|
|
|
|
|
|
|
|
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
|
2013-12-04 19:03:32 +09:00
|
|
|
private final ConcurrentMap<String, Boolean> reportedLeaks = PlatformDependent.newConcurrentHashMap();
|
2013-01-11 22:47:54 +09:00
|
|
|
|
|
|
|
private final String resourceType;
|
|
|
|
private final int samplingInterval;
|
2015-10-28 11:46:28 +01:00
|
|
|
private final int mask;
|
2013-01-11 22:47:54 +09:00
|
|
|
private final long maxActive;
|
|
|
|
private long active;
|
|
|
|
private final AtomicBoolean loggedTooManyActive = new AtomicBoolean();
|
|
|
|
|
2013-02-09 12:27:38 +09:00
|
|
|
private long leakCheckCnt;
|
2013-01-11 22:47:54 +09:00
|
|
|
|
2016-06-28 21:20:40 +02:00
|
|
|
/**
|
|
|
|
* @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
|
|
|
|
*/
|
|
|
|
@Deprecated
|
2013-01-11 22:47:54 +09:00
|
|
|
public ResourceLeakDetector(Class<?> resourceType) {
|
2013-12-04 19:03:32 +09:00
|
|
|
this(simpleClassName(resourceType));
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
|
2016-06-28 21:20:40 +02:00
|
|
|
/**
|
|
|
|
* @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
|
|
|
|
*/
|
|
|
|
@Deprecated
|
2013-01-11 22:47:54 +09:00
|
|
|
public ResourceLeakDetector(String resourceType) {
|
|
|
|
this(resourceType, DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE);
|
|
|
|
}
|
|
|
|
|
2016-06-28 21:20:40 +02:00
|
|
|
/**
|
|
|
|
* This should not be used directly by users of {@link ResourceLeakDetector}.
|
|
|
|
* Please use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class)}
|
|
|
|
* or {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("deprecation")
|
2013-01-11 22:47:54 +09:00
|
|
|
public ResourceLeakDetector(Class<?> resourceType, int samplingInterval, long maxActive) {
|
2013-12-04 19:03:32 +09:00
|
|
|
this(simpleClassName(resourceType), samplingInterval, maxActive);
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
|
2016-06-28 21:20:40 +02:00
|
|
|
/**
|
|
|
|
* @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
|
|
|
|
*/
|
|
|
|
@Deprecated
|
2013-01-11 22:47:54 +09:00
|
|
|
public ResourceLeakDetector(String resourceType, int samplingInterval, long maxActive) {
|
|
|
|
if (resourceType == null) {
|
|
|
|
throw new NullPointerException("resourceType");
|
|
|
|
}
|
|
|
|
if (maxActive <= 0) {
|
|
|
|
throw new IllegalArgumentException("maxActive: " + maxActive + " (expected: 1+)");
|
|
|
|
}
|
|
|
|
|
|
|
|
this.resourceType = resourceType;
|
2016-07-29 09:09:29 -07:00
|
|
|
this.samplingInterval = MathUtil.safeFindNextPositivePowerOfTwo(samplingInterval);
|
2015-10-28 11:46:28 +01:00
|
|
|
// samplingInterval is a power of two so we calculate a mask that we can use to
|
|
|
|
// check if we need to do any leak detection or not.
|
|
|
|
mask = this.samplingInterval - 1;
|
2013-01-11 22:47:54 +09:00
|
|
|
this.maxActive = maxActive;
|
|
|
|
|
|
|
|
head.next = tail;
|
|
|
|
tail.prev = head;
|
|
|
|
}
|
|
|
|
|
2013-07-23 14:06:58 +09:00
|
|
|
/**
|
|
|
|
* Creates a new {@link ResourceLeak} which is expected to be closed via {@link ResourceLeak#close()} when the
|
|
|
|
* related resource is deallocated.
|
|
|
|
*
|
|
|
|
* @return the {@link ResourceLeak} or {@code null}
|
|
|
|
*/
|
2016-06-28 21:20:40 +02:00
|
|
|
public final ResourceLeak open(T obj) {
|
2013-12-04 19:03:32 +09:00
|
|
|
Level level = ResourceLeakDetector.level;
|
|
|
|
if (level == Level.DISABLED) {
|
2013-07-23 14:06:58 +09:00
|
|
|
return null;
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
|
2013-12-04 19:03:32 +09:00
|
|
|
if (level.ordinal() < Level.PARANOID.ordinal()) {
|
2016-09-28 14:35:25 -07:00
|
|
|
if ((++ leakCheckCnt & mask) == 0) {
|
2013-12-04 19:03:32 +09:00
|
|
|
reportLeak(level);
|
|
|
|
return new DefaultResourceLeak(obj);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
reportLeak(level);
|
|
|
|
return new DefaultResourceLeak(obj);
|
|
|
|
}
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
|
2013-12-04 19:03:32 +09:00
|
|
|
private void reportLeak(Level level) {
|
|
|
|
if (!logger.isErrorEnabled()) {
|
2013-01-11 22:47:54 +09:00
|
|
|
for (;;) {
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
|
|
|
|
if (ref == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ref.close();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Report too many instances.
|
2013-12-04 19:03:32 +09:00
|
|
|
int samplingInterval = level == Level.PARANOID? 1 : this.samplingInterval;
|
2013-01-11 22:47:54 +09:00
|
|
|
if (active * samplingInterval > maxActive && loggedTooManyActive.compareAndSet(false, true)) {
|
2016-06-15 00:19:15 -07:00
|
|
|
reportInstancesLeak(resourceType);
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
// Detect and report previous leaks.
|
|
|
|
for (;;) {
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
|
|
|
|
if (ref == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-02-09 12:21:30 +09:00
|
|
|
ref.clear();
|
|
|
|
|
2013-01-11 22:47:54 +09:00
|
|
|
if (!ref.close()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-12-04 19:03:32 +09:00
|
|
|
String records = ref.toString();
|
|
|
|
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
|
|
|
|
if (records.isEmpty()) {
|
2016-06-15 00:19:15 -07:00
|
|
|
reportUntracedLeak(resourceType);
|
2013-12-04 19:03:32 +09:00
|
|
|
} else {
|
2016-06-15 00:19:15 -07:00
|
|
|
reportTracedLeak(resourceType, records);
|
2013-12-04 19:03:32 +09:00
|
|
|
}
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-15 00:19:15 -07:00
|
|
|
/**
|
|
|
|
* 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));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This method is called when instance leaks are detected. It can be overridden for tracking how many times leaks
|
|
|
|
* have been detected.
|
|
|
|
*/
|
|
|
|
protected void reportInstancesLeak(String resourceType) {
|
|
|
|
logger.error("LEAK: You are creating too many " + resourceType + " instances. " +
|
|
|
|
resourceType + " is a shared resource that must be reused across the JVM," +
|
|
|
|
"so that only a few instances are created.");
|
|
|
|
}
|
|
|
|
|
2013-02-09 12:14:52 +09:00
|
|
|
private final class DefaultResourceLeak extends PhantomReference<Object> implements ResourceLeak {
|
2013-12-04 19:03:32 +09:00
|
|
|
private final String creationRecord;
|
|
|
|
private final Deque<String> lastRecords = new ArrayDeque<String>();
|
2013-01-11 22:47:54 +09:00
|
|
|
private final AtomicBoolean freed;
|
|
|
|
private DefaultResourceLeak prev;
|
|
|
|
private DefaultResourceLeak next;
|
2016-02-10 09:42:58 -08:00
|
|
|
private int removedRecords;
|
2013-01-11 22:47:54 +09:00
|
|
|
|
2014-01-29 11:52:28 +09:00
|
|
|
DefaultResourceLeak(Object referent) {
|
2013-01-11 22:47:54 +09:00
|
|
|
super(referent, referent != null? refQueue : null);
|
|
|
|
|
|
|
|
if (referent != null) {
|
2013-12-04 19:03:32 +09:00
|
|
|
Level level = getLevel();
|
|
|
|
if (level.ordinal() >= Level.ADVANCED.ordinal()) {
|
2014-01-29 11:44:59 +09:00
|
|
|
creationRecord = newRecord(null, 3);
|
2013-12-04 19:03:32 +09:00
|
|
|
} else {
|
|
|
|
creationRecord = null;
|
|
|
|
}
|
2013-01-11 22:47:54 +09:00
|
|
|
|
|
|
|
// TODO: Use CAS to update the list.
|
|
|
|
synchronized (head) {
|
|
|
|
prev = head;
|
|
|
|
next = head.next;
|
|
|
|
head.next.prev = this;
|
|
|
|
head.next = this;
|
|
|
|
active ++;
|
|
|
|
}
|
|
|
|
freed = new AtomicBoolean();
|
|
|
|
} else {
|
2013-12-04 19:03:32 +09:00
|
|
|
creationRecord = null;
|
2013-01-11 22:47:54 +09:00
|
|
|
freed = new AtomicBoolean(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-04 19:03:32 +09:00
|
|
|
@Override
|
|
|
|
public void record() {
|
2014-01-29 11:44:59 +09:00
|
|
|
record0(null, 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void record(Object hint) {
|
|
|
|
record0(hint, 3);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void record0(Object hint, int recordsToSkip) {
|
2013-12-04 19:03:32 +09:00
|
|
|
if (creationRecord != null) {
|
2014-01-29 11:44:59 +09:00
|
|
|
String value = newRecord(hint, recordsToSkip);
|
2013-12-04 19:03:32 +09:00
|
|
|
|
|
|
|
synchronized (lastRecords) {
|
|
|
|
int size = lastRecords.size();
|
|
|
|
if (size == 0 || !lastRecords.getLast().equals(value)) {
|
|
|
|
lastRecords.add(value);
|
|
|
|
}
|
|
|
|
if (size > MAX_RECORDS) {
|
|
|
|
lastRecords.removeFirst();
|
2016-02-10 09:42:58 -08:00
|
|
|
++removedRecords;
|
2013-12-04 19:03:32 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-11 22:47:54 +09:00
|
|
|
@Override
|
|
|
|
public boolean close() {
|
|
|
|
if (freed.compareAndSet(false, true)) {
|
|
|
|
synchronized (head) {
|
|
|
|
active --;
|
|
|
|
prev.next = next;
|
|
|
|
next.prev = prev;
|
|
|
|
prev = null;
|
|
|
|
next = null;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2013-12-04 19:03:32 +09:00
|
|
|
|
2015-01-08 12:43:03 +09:00
|
|
|
@Override
|
2013-12-04 19:03:32 +09:00
|
|
|
public String toString() {
|
|
|
|
if (creationRecord == null) {
|
2016-02-10 09:42:58 -08:00
|
|
|
return EMPTY_STRING;
|
2013-12-04 19:03:32 +09:00
|
|
|
}
|
|
|
|
|
2016-02-10 09:42:58 -08:00
|
|
|
final Object[] array;
|
|
|
|
final int removedRecords;
|
2013-12-06 22:35:14 +09:00
|
|
|
synchronized (lastRecords) {
|
|
|
|
array = lastRecords.toArray();
|
2016-02-10 09:42:58 -08:00
|
|
|
removedRecords = this.removedRecords;
|
2013-12-06 22:35:14 +09:00
|
|
|
}
|
2013-12-04 19:03:32 +09:00
|
|
|
|
2016-02-10 09:42:58 -08:00
|
|
|
StringBuilder buf = new StringBuilder(16384).append(NEWLINE);
|
|
|
|
if (removedRecords > 0) {
|
|
|
|
buf.append("WARNING: ")
|
|
|
|
.append(removedRecords)
|
|
|
|
.append(" leak records were discarded because the leak record count is limited to ")
|
|
|
|
.append(MAX_RECORDS)
|
|
|
|
.append(". Use system property ")
|
|
|
|
.append(PROP_MAX_RECORDS)
|
|
|
|
.append(" to increase the limit.")
|
2014-11-09 01:46:30 +03:00
|
|
|
.append(NEWLINE);
|
2016-02-10 09:42:58 -08:00
|
|
|
}
|
|
|
|
buf.append("Recent access records: ")
|
|
|
|
.append(array.length)
|
|
|
|
.append(NEWLINE);
|
2013-12-04 19:03:32 +09:00
|
|
|
|
2013-12-06 22:35:14 +09:00
|
|
|
if (array.length > 0) {
|
|
|
|
for (int i = array.length - 1; i >= 0; i --) {
|
2014-11-09 01:46:30 +03:00
|
|
|
buf.append('#')
|
|
|
|
.append(i + 1)
|
|
|
|
.append(':')
|
|
|
|
.append(NEWLINE)
|
|
|
|
.append(array[i]);
|
2013-12-04 19:03:32 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-09 01:46:30 +03:00
|
|
|
buf.append("Created at:")
|
|
|
|
.append(NEWLINE)
|
|
|
|
.append(creationRecord);
|
2013-12-04 19:03:32 +09:00
|
|
|
|
2014-11-09 01:46:30 +03:00
|
|
|
buf.setLength(buf.length() - NEWLINE.length());
|
2013-12-04 19:03:32 +09:00
|
|
|
return buf.toString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-29 11:52:28 +09:00
|
|
|
private static final String[] STACK_TRACE_ELEMENT_EXCLUSIONS = {
|
2014-01-29 11:44:59 +09:00
|
|
|
"io.netty.util.ReferenceCountUtil.touch(",
|
|
|
|
"io.netty.buffer.AdvancedLeakAwareByteBuf.touch(",
|
2014-01-29 11:52:28 +09:00
|
|
|
"io.netty.buffer.AbstractByteBufAllocator.toLeakAwareBuffer(",
|
2015-08-28 15:05:52 -07:00
|
|
|
"io.netty.buffer.AdvancedLeakAwareByteBuf.recordLeakNonRefCountingOperation("
|
2014-01-29 11:52:28 +09:00
|
|
|
};
|
|
|
|
|
2014-01-29 11:44:59 +09:00
|
|
|
static String newRecord(Object hint, int recordsToSkip) {
|
2013-12-04 19:03:32 +09:00
|
|
|
StringBuilder buf = new StringBuilder(4096);
|
2014-01-29 11:44:59 +09:00
|
|
|
|
|
|
|
// Append the hint first if available.
|
|
|
|
if (hint != null) {
|
|
|
|
buf.append("\tHint: ");
|
|
|
|
// Prefer a hint string to a simple string form.
|
|
|
|
if (hint instanceof ResourceLeakHint) {
|
|
|
|
buf.append(((ResourceLeakHint) hint).toHintString());
|
|
|
|
} else {
|
|
|
|
buf.append(hint);
|
|
|
|
}
|
|
|
|
buf.append(NEWLINE);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append the stack trace.
|
2013-12-04 19:03:32 +09:00
|
|
|
StackTraceElement[] array = new Throwable().getStackTrace();
|
|
|
|
for (StackTraceElement e: array) {
|
|
|
|
if (recordsToSkip > 0) {
|
|
|
|
recordsToSkip --;
|
|
|
|
} else {
|
2014-01-29 11:52:28 +09:00
|
|
|
String estr = e.toString();
|
|
|
|
|
|
|
|
// Strip the noisy stack trace elements.
|
|
|
|
boolean excluded = false;
|
|
|
|
for (String exclusion: STACK_TRACE_ELEMENT_EXCLUSIONS) {
|
|
|
|
if (estr.startsWith(exclusion)) {
|
|
|
|
excluded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!excluded) {
|
|
|
|
buf.append('\t');
|
|
|
|
buf.append(estr);
|
|
|
|
buf.append(NEWLINE);
|
|
|
|
}
|
2013-12-04 19:03:32 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return buf.toString();
|
2013-01-11 22:47:54 +09:00
|
|
|
}
|
|
|
|
}
|