preface
If you do not return pooled resources, they may be leaked, such as database connections. For Netty memory pools, memory leaks can occur if a memory block is used and not returned. Netty provides the leak detection service to help users troubleshoot memory leaks. Learn from several angles in this chapter:
- How to use Netty leak detection
- How can I properly configure the Netty leak detection level
- How to implement Netty leak detection
I. Use of leak detection
public class MyLeakTest {
public static final int _1MB = 1024 * 1024;
public static final int _17MB = 17 * 1024 * 1024;
@Before
public void init(a) {
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(() -> {
long memory = PlatformDependent.usedDirectMemory()/ _1MB;
System.out.println(Use direct memory: + memory + "MB");
}, 0.1, TimeUnit.SECONDS);
}
@Test
public void testLeak01(a) throws InterruptedException {
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
for (int i = 0; i < 3; i++) {
allocate17();
System.gc();
Thread.sleep(1000); }}private void allocate17(a) {
System.out.println("Start allocating");
PooledByteBufAllocator allocator = (PooledByteBufAllocator) ByteBufAllocator.DEFAULT;
allocator.newDirectBuffer(_17MB, Integer.MAX_VALUE)/*.release()*/;
System.out.println("Successful distribution"); }}Copy the code
The init method starts a thread responsible for printing the immediate memory currently in use. TestLeak01 Method simulates a memory leak.
TestLeak01 method first by ResourceLeakDetector. SetLevel method sets the level at the Netty leak detection PARANOID, This step can also use * * ty – Dio.net. LeakDetection. * * = the paranoid level Settings. Then allocate 17MB in a loop without calling ByteBuf’s release method and calling System.gc notification for garbage collection.
Console output:
Start using direct memory distribution success: 17 MB distribution 14:12:51. 719 ERROR [main] io.net ty. Util. ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information. Recent access records: Created at: io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:372) io.netty.buffer.MyLeakTest.allocate17(MyLeakTest.java:65) io.netty.buffer.MyLeakTest.testLeak01(MyLeakTest.java:40) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) Sun. Reflect. NativeMethodAccessorImpl. Invoke (NativeMethodAccessorImpl. Java: 62) successful distribution using direct memory: 34 MB distribution success using direct memory: 51 MBCopy the code
If we use the SIMPLE or ADVANCED levels, it will take several memory allocations before this exception log is printed.
@Test
public void testLeak03(a) throws InterruptedException {
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
for (int i = 0; i < 300; i++) {
allocate1KB(); / / assign 1 KB
System.gc();
Thread.sleep(100); }}private void allocate1KB(int count) {
System.out.println("Start distribution, number one." + count + "Time");
PooledByteBufAllocator allocator = (PooledByteBufAllocator) ByteBufAllocator.DEFAULT;
allocator.newDirectBuffer(1024, Integer.MAX_VALUE)/*.release()*/;
System.out.println("Successful allocation, no." + count + "Time");
}
Copy the code
Console output:
Direct memory 243rd use :16MB start allocate, 244th use successful allocate, 244th use... Start distribution, the 251th successful distribution, distribution of 251th start, 252th 14:21:33. 063 ERROR [main] io.net ty. Util. ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information. Recent access records: Created at: io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:372) io.netty.buffer.MyLeakTest.allocate1KB(MyLeakTest.java:75) io.netty.buffer.MyLeakTest.testLeak03(MyLeakTest.java:66) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) Sun. Reflect. NativeMethodAccessorImpl. Invoke (NativeMethodAccessorImpl. Java: 62) distribution of success, for the 252th time using direct memory: 16 MB distribution, for the 253th timeCopy the code
Why is the direct memory here always 16MB? Recall the process of Netty memory allocation. Netty applies for 16MB memory at a time from the system. The subsequent memory allocation is based on Chunk allocation or Subpage allocation, and does not apply for memory from the system.
Two, leak detection level
Leak detection Level corresponding enumeration types ResourceLeakDetector. Level.
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;
}
Copy the code
Different leak detection levels correspond to different leak detection behaviors.
level | Open detection probability | Corresponds to the ByteBuf implementation class |
---|---|---|
DISABLED | 0% | – |
SIMPLE | The default 1/128 | SimpleLeakAwareByteBuf |
ADVANCED | The default 1/128 | AdvancedLeakAwareByteBuf |
PARANOID | 100% | AdvancedLeakAwareByteBuf |
By default, the leak detection level is SIMPLE, the sample rate for SIMPLE and ADVANCED is 1/128, and the maximum Record for a single leak place is 4.
private static final Level DEFAULT_LEVEL = Level.SIMPLE;
private static final int DEFAULT_TARGET_RECORDS = 4;
private static final int DEFAULT_SAMPLING_INTERVAL = 128;
private static Level level;
static {
final boolean disabled;
if (SystemPropertyUtil.get("io.netty.noResourceLeakDetection") != null) {
disabled = SystemPropertyUtil.getBoolean("io.netty.noResourceLeakDetection".false);
} else {
disabled = false;
}
/ / io.net. Ty noResourceLeakDetection default to false, the default level is SIMPLE
Level defaultLevel = disabled? Level.DISABLED : DEFAULT_LEVEL;
// First read old property name
String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name());
// If new property name is present, use it
levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr);
Level level = Level.parseLevel(levelStr);
/ / for a place to leak, RECORD a few RECORD, most of the default io.net. Ty leakDetection. TargetRecords = 4
TARGET_RECORDS = SystemPropertyUtil.getInt(PROP_TARGET_RECORDS, DEFAULT_TARGET_RECORDS);
/ / leak detection sampling rate ty. The default io.net leakDetection. SamplingInterval = 128
SAMPLING_INTERVAL = SystemPropertyUtil.getInt(PROP_SAMPLING_INTERVAL, DEFAULT_SAMPLING_INTERVAL);
ResourceLeakDetector.level = level;
}
Copy the code
The leak detection implementation classes are all subclasses of WrappedByteBuf, WrappedByteBuf wraps a ByteBuf, and all ByteBuf interfaces delegate to the ByteBuf implementation. For SIMPLE levels, select SimpleLeakAwareByteBuf package leak detection. For levels higher than SIMPLE, select AdvancedLeakAwareByteBuf package leak detection.
For details, see PooledByteBufAllocator#newDirectBuffer, PooledByteBufAllocator#newHeapBuffer, and UnpooledByteBufAllocator#newDirectBuffe Entrance r. PooledByteBufAllocator#newDirectBuffer is used as an example.
@Override
protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {
// 1. Get the PoolArena for the current thread cache and thread cache
PoolThreadCache cache = threadCache.get();
PoolArena<ByteBuffer> directArena = cache.directArena;
final ByteBuf buf;
if(directArena ! =null) {
// 2. Select pool
buf = directArena.allocate(cache, initialCapacity, maxCapacity);
} else {
// Select unpooled
buf = PlatformDependent.hasUnsafe() ?
UnsafeByteBufUtil.newUnsafeDirectByteBuf(this, initialCapacity, maxCapacity) :
new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);
}
// 3. If memory leak detection is configured, wrap ByteBuf
return toLeakAwareBuffer(buf);
}
Copy the code
In the last step, newDirectBuffer wraps PooledByteBuf as LeakAware ByteBuf. Based on the Level field of the ResourceLeakDetector class, determine which LeakAwareByteBuf is instantiated and a ResourceLeakTracker instance is passed in during construction.
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;
}
Copy the code
Note that the ResourceLeakTracker instance is created by the AbstractByteBuf class variable leakDetector’s Track method.
public abstract class AbstractByteBuf extends ByteBuf {
static final ResourceLeakDetector<ByteBuf> leakDetector = ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);
}
Copy the code
SimpleLeakAwareByteBuf Simple leak detection that does not record additional information about memory leaks. The feature is that leak detection is turned off using the Close method of ResourceLeakTracker only when the Release method is called.
class SimpleLeakAwareByteBuf extends WrappedByteBuf {
// ByteBuf participating in memory leak detection
private final ByteBuf trackedByteBuf;
final ResourceLeakTracker<ByteBuf> leak;
SimpleLeakAwareByteBuf(ByteBuf wrapped, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leak) {
super(wrapped);
this.trackedByteBuf = ObjectUtil.checkNotNull(trackedByteBuf, "trackedByteBuf");
this.leak = ObjectUtil.checkNotNull(leak, "leak");
}
@Override
public boolean release(a) {
if (super.release()) {
closeLeak();
return true;
}
return false;
}
@Override
public boolean release(int decrement) {
if (super.release(decrement)) {
closeLeak();
return true;
}
return false;
}
// When release is called correctly, leak detection is turned off
private void closeLeak(a) {
boolean closed = leak.close(trackedByteBuf);
assertclosed; }}Copy the code
AdvancedLeakAwareByteBuf inherits SimpleLeakAwareByteBuf, and records Record information when ByteBuf’s API is executed, facilitating subsequent troubleshooting. ResourceLeakTracker’s record method comes later.
final class AdvancedLeakAwareByteBuf extends SimpleLeakAwareByteBuf {
private static final String PROP_ACQUIRE_AND_RELEASE_ONLY = "io.netty.leakDetection.acquireAndReleaseOnly";
// Whether to record only when the record is acquired and released. Default is false
private static final boolean ACQUIRE_AND_RELEASE_ONLY;
static {
ACQUIRE_AND_RELEASE_ONLY = SystemPropertyUtil.getBoolean(PROP_ACQUIRE_AND_RELEASE_ONLY, false);
}
AdvancedLeakAwareByteBuf(ByteBuf buf, ResourceLeakTracker<ByteBuf> leak) {
super(buf, leak);
}
AdvancedLeakAwareByteBuf(ByteBuf wrapped, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leak) {
super(wrapped, trackedByteBuf, leak);
}
// ACQUIRE_AND_RELEASE_ONLY Is false when the Record is leaked
static void recordLeakNonRefCountingOperation(ResourceLeakTracker<ByteBuf> leak) {
if (!ACQUIRE_AND_RELEASE_ONLY) {
leak.record();
}
}
// Read & write if ACQUIRE_AND_RELEASE_ONLY is false, then leak.record is executed
@Override
public byte getByte(int index) {
recordLeakNonRefCountingOperation(leak);
return super.getByte(index);
}
@Override
public ByteBuf writeByte(int value) {
recordLeakNonRefCountingOperation(leak);
return super.writeByte(value);
}
// retain (increase reference count) /release (decrease reference count) /touch(specifically for a record)
// Execute leak.record directly
@Override
public ByteBuf retain(a) {
leak.record();
return super.retain();
}
@Override
public boolean release(a) {
leak.record();
return super.release();
}
@Override
public ByteBuf touch(a) {
leak.record();
return this; }}Copy the code
Third, Record
Record represents a Record, linked list data structure, and is an internal class of Resource Detector.
private static final class Record extends Throwable {
// The last node of the Record list
private static final Record BOTTOM = new Record();
// External incoming hint. ToString to record additional information about memory leaks
private final String hintString;
// Point to the next Record
private final Record next;
// The current Record is in the list position 0-n, only the BOTTOM node is -1
private final int pos;
Record(Record next, Object hint) {
hintString = 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(a) {
hintString = null;
next = null;
pos = -1; }}Copy the code
Record inherits Throwable, omitting some of the code, and you can see that the Throwable is inherited to get the call stack (getStackTrace).
@Override
public String toString(a) {
StringBuilder buf = new StringBuilder(2048);
if(hintString ! =null) {
buf.append("\tHint: ").append(hintString).append(NEWLINE);
}
// From Throwable, you can print StackTrace
StackTraceElement[] array = getStackTrace();
Skip the first three stacks
for (int i = 3; i < array.length; i++) {
StackTraceElement element = array[i];
buf.append('\t');
buf.append(element.toString());
buf.append(NEWLINE);
}
return buf.toString();
}
Copy the code
Take a unit test to see what Record does.
@Test
public void testRecord(a) throws InterruptedException {
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
PooledByteBufAllocator allocator = (PooledByteBufAllocator) ByteBufAllocator.DEFAULT;
// Created at
ByteBuf byteBuf = allocator.newDirectBuffer(1024, Integer.MAX_VALUE);
/ / # 2
byteBuf.touch();
/ / # 1
ByteBuf slice = byteBuf.slice();
// Release strong references, notify GC
slice = null;
byteBuf = null;
System.gc();
Thread.sleep(1000);
// To trigger leak detection log printing
byteBuf = allocator.newDirectBuffer(1024, Integer.MAX_VALUE);
}
Copy the code
Console output:
Direct memory :0MB direct memory :16MB14:18:34.888 [main] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected. See https://netty.io/wiki/reference-counted-objects.html for more information. Recent access records: #1: io.netty.buffer.AdvancedLeakAwareByteBuf.slice(AdvancedLeakAwareByteBuf.java:76) io.netty.buffer.MyLeakTest.testRecord(MyLeakTest.java:80) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) #2: io.netty.buffer.MyLeakTest.testRecord(MyLeakTest.java:79) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) Created at: io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:372) io.netty.buffer.MyLeakTest.testRecord(MyLeakTest.java:78) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)Copy the code
You can see that the output records contains three sections
- #1: Stack information for the slice method call
- #2: Stack information for the touch method call
- Created at: Bytebuf Created stack information
Four, DefaultResourceLeak
The ResourceLeakTracker interface is exposed for use by clients, providing the ability to Record records on the one hand and to turn off leak detection for an object on the other.
public interface ResourceLeakTracker<T> {
// Record a Record
void record(a);
// Record a Record with some hint information to help troubleshoot
void record(Object hint);
// Turn off memory leak detection for trackedObject, which is usually a ByteBuf instance
boolean close(T trackedObject);
}
Copy the code
ResourceLeakTracker the default implementation is DefaultResourceLeak.
private static final class DefaultResourceLeak<T>
extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
// Atomic updater for head pointer
private static finalAtomicReferenceFieldUpdater<DefaultResourceLeak<? >, Record> headUpdater = (AtomicReferenceFieldUpdater) AtomicReferenceFieldUpdater.newUpdater(DefaultResourceLeak.class, Record.class,"head");
// Discard atomic updater for droppedRecords number of records
private static finalAtomicIntegerFieldUpdater<DefaultResourceLeak<? >> droppedRecordsUpdater = (AtomicIntegerFieldUpdater) AtomicIntegerFieldUpdater.newUpdater(DefaultResourceLeak.class,"droppedRecords");
// Record list for T leak detection
private volatile Record head;
// Discard Record number
private volatile int droppedRecords;
// Contains all instances of DefaultResourceLeak, passed in externally
private finalSet<DefaultResourceLeak<? >> allLeaks;// Hash value of T instance
private final inttrackedHash; DefaultResourceLeak(Object referent, ReferenceQueue<Object> refQueue, Set<DefaultResourceLeak<? >> allLeaks) {// Call WeakReference constructor, pass in reference object and reference queue
super(referent, refQueue);
// Evaluates the hash value of the reference object
trackedHash = System.identityHashCode(referent);
// Add the current instance to allLeaks
allLeaks.add(this);
// Set the head node of the Record list to record.bottom
headUpdater.set(this.new Record(Record.BOTTOM));
this.allLeaks = allLeaks; }}Copy the code
The ResourceLeak interface is obsolete and can be ignored. The key point is that DefaultResourceLeak inherits WeakReference, passing in external reference object and reference queue through WeakReference structure. This reference object is the ByteBuf instance monitored for Netty memory leak detection.
1, record0
The record0 method is responsible for recording a Record. Abstract methods for Record () and Record (T) that implement the ResourceLeakTracker interface.
private void record0(Object hint) {
if (TARGET_RECORDS > 0) {
Record oldHead;
Record prevHead;
Record newHead;
boolean dropped;
do {
// If the head node is empty, the close method has been called, the resource has been freed, and no need to continue
if ((prevHead = oldHead = headUpdater.get(this)) = =null) {
return;
}
// Number of current records
final int numElements = oldHead.pos + 1;
/ / if the current Record number greater than io.net. Ty leakDetection. TargetRecords (4) by default, may list a Record of the head
if (numElements >= TARGET_RECORDS) {
// Discard probability = 1/2 ^ (numElements - targetRecords)
final int backOffFactor = Math.min(numElements - TARGET_RECORDS, 30);
if (dropped = PlatformDependent.threadLocalRandom().nextInt(1<< backOffFactor) ! =0) { prevHead = oldHead.next; }}else {
dropped = false;
}
// cas sets the list head node to the newly created RecordnewHead = hint ! =null ? new Record(prevHead, hint) : new Record(prevHead);
} while(! headUpdater.compareAndSet(this, oldHead, newHead));
if (dropped) {
droppedRecordsUpdater.incrementAndGet(this); }}}Copy the code
The record0 method basically constructs a new Record and inserts the CAS header into the Record list. It is worth noting that after four records are inserted, each new Record may cause the linked list head node to be discarded.
2, close
The close method is responsible for turning off leak detection for the current reference object. Implement the close(T) abstract method of the Resource Tracker interface.
private static finalAtomicReferenceFieldUpdater<DefaultResourceLeak<? >, Record> headUpdater = (AtomicReferenceFieldUpdater) AtomicReferenceFieldUpdater.newUpdater(DefaultResourceLeak.class, Record.class,"head");
private finalSet<DefaultResourceLeak<? >> allLeaks;private final int trackedHash;
@Override
public boolean close(a) {
// Remove the current DefaultResourceLeak instance from Set
if (allLeaks.remove(this)) {
// Call weakReference. clear to remove the referent
clear();
// Set the head of the Record list to null
headUpdater.set(this.null);
return true;
}
return false;
}
@Override
public boolean close(T trackedObject) {
// Ensure that the incoming instance and the monitored instance are the same instance
assert trackedHash == System.identityHashCode(trackedObject);
try {
return close();
} finally{ reachabilityFence0(trackedObject); }}Copy the code
The close method does three things:
- Remove the current instance from allLeaks
- Call the clear method of parent class WeakReference to set the reference object as empty
- Set the head node of Record to null
Take a look at the reachabilityFence0 method, which prevents the current DefaultResourceLeak instance from being recycled early. May lead to determine in advance before because JDK9 objects inaccessible, lead to early recovery, here by the finally + synchronized that objects will not be early recovery, JDK9 provides the Reference. ReachabilityFence method to solve this problem.
/**
* Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable.
* see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8
* The Java 9 method Reference.reachabilityFence offers a solution to this problem.
*/
private static void reachabilityFence0(Object ref) {
if(ref ! =null) {
synchronized (ref) {
// Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521}}}Copy the code
3, the dispose
The dipose method is a non-public method, which is used by Netty to check whether the close method of DefaultResourceLeak is called by the client. If the close method is called, it means that the monitored resources are collected normally (ByteBuf’s release method is called normally). The current DefaultResourceLeak instance should not exist in the allLeaks collection.
/** * @return true Close not executed, memory leak * false Close executed, no memory leak */ Boolean dispose() {clear(); return allLeaks.remove(this); }Copy the code
The reason why we use a Set to determine if DefaultResourceLeak instances are called close normally, rather than a Boolean, is to ensure that DefaultResourceLeak is strongly referenced and not GC.
Fifth, ResourceLeakDetector
ResourceLeakDetector manages leak detection for a class of instances (generic T).
public class ResourceLeakDetector<T> {
// A collection of all leak detection tasks currently managed by Detector
private finalSet<DefaultResourceLeak<? >> allLeaks = Collections.newSetFromMap(newConcurrentHashMap<DefaultResourceLeak<? >, Boolean>());// Reference queue, where DefaultResourceLeak instances can be received when the detection object of the DefaultResourceLeak package is reclaimed
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
// A collection of report strings that have done memory leak report output
private final Set<String> reportedLeaks =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
/ / io.net ty. LeakDetection. SamplingInterval 128 by default
private final int samplingInterval;
}
Copy the code
ResourceLeakDetector mainly does the following three things:
- Establish the relationship between the detection object and ResourceLeakTracker to enable leak detection
- Detect leaks
- Let the cat out of the report
All three things are done in the track method, and for Netty memory leak detection, all three things are done when ByteBuf is created.
This is why the last line of the unit test on Record in section 3 allocates memory once, because only when ByteBuf is allocated, the toLeakAwareBuffer method will be called, and the toLeakAwareBuffer method will call track method, which can do a leak detection and print a leak report.
private DefaultResourceLeak track0(T obj) {
Level level = ResourceLeakDetector.level;
if (level == Level.DISABLED) {
return null;
}
// If level is less than PARANOID, DefaultResourceLeak is not necessarily returned
/ / depends on random PlatformDependent. ThreadLocalRandom () nextInt (samplingInterval)
if (level.ordinal() < Level.PARANOID.ordinal()) {
// a 1 in 128 chance of doing a leak check and returning DefaultResourceLeak
if ((PlatformDependent.threadLocalRandom().nextInt(samplingInterval)) == 0) {
reportLeak();
return new DefaultResourceLeak(obj, refQueue, allLeaks);
}
return null;
}
// Level equals PARANOID Do a leak check immediately and return DefaultResourceLeak
reportLeak();
return new DefaultResourceLeak(obj, refQueue, allLeaks);
}
Copy the code
Track method has slightly different behaviors according to different detection levels. The difference is that if the PARANOID level is lower than the PARANOID level, a leak detection + leak report + OBJ leak detection may not be performed simultaneously.
In the track method, reportLeak = leak detection + leak report; New DefaultResourceLeak = Enable leak detection for OBJ. The constructor for DefaultResourceLeak was looked at in Section 4, where we focus on the reportLeak method.
private void reportLeak(a) {
// Whether to enable leak detection (currently based on whether the log level allows error control)
if(! needReport()) {/ / empty refQueue
clearRefQueue();
return;
}
for (;;) {
// Get the DefaultResourceLeak from ReferenceQueue
// DefaultResourceLeak the obJ (ByteBuf instance) wrapped as a virtual reference has been reclaimed at this point
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
// Determine if a leak has occurred
if(! ref.dispose()) {continue;
}
// Prints leak logs
String records = ref.toString();
// If records has already been output, it will not be output again
if (reportedLeaks.add(records)) {
if (records.isEmpty()) {
reportUntracedLeak(resourceType);
} else{ reportTracedLeak(resourceType, records); }}}}Copy the code
First, the reportLeak method is controlled by the log level. If the log level is less than Error, leak detection will not be done.
protected boolean needReport(a) {
return logger.isErrorEnabled();
}
Copy the code
Next, the DefaultResourceLeak virtual reference instance is pulled through the reference queue.
If you obtain DefaultResourceLeak, the referenced instances monitored by DefaultResourceLeak instances have been garbage collected. Use the Dispose method to determine whether close is invoked to close the instance.
If a leak occurs, check whether the output of the toString method of DefaultResourceLeak is in the reportedLeaks set. If there is, it indicates that the leak has been output and the leak report will not be output again. Otherwise, the leak report will be output.
Six, doubt
Why use an allLeaks Set to use the remove method instead of a Boolean to determine if the resource is properly released?
private finalSet<DefaultResourceLeak<? >> allLeaks;private final inttrackedHash; DefaultResourceLeak( Object referent, ReferenceQueue<Object> refQueue, Set<DefaultResourceLeak<? >> allLeaks) {super(referent, refQueue);
trackedHash = System.identityHashCode(referent);
// Note that this allLeaks is external
// Is the allLeaks collection of ResourceLeakDetector
allLeaks.add(this);
this.allLeaks = allLeaks;
}
/** * Netty calls this method to determine whether the close method was properly called on the object instance. *@returnTrue If close is not executed, there is a memory leak. * False If close is executed, there is no memory leak. */
boolean dispose(a) {
clear();
return allLeaks.remove(this);
}
/** * is exposed to the client call, which is called when the monitor object is released normally */
@Override
public boolean close(a) {
// Remove the current DefaultResourceLeak instance from Set
if (allLeaks.remove(this)) {
// Call clear so the reference is not even enqueued.
// Call weakReference. clear to remove the referent
clear();
// Set the head of the Record list to null
headUpdater.set(this.null);
return true;
}
return false;
}
Copy the code
If DefaultResourceLeak instances are not strongly referenced by Set, DefaultResourceLeak is directly GC if ByteBuf does not have strong references.
For example, if SimpleLeakAwareByteBuf holds DefaultResourceLeak instances, if SimpleLeakAwareByteBuf is not strongly referenced, DefaultResourceLeak instances are also collected and memory leaks cannot be detected.
class SimpleLeakAwareByteBuf extends WrappedByteBuf {
// The detected ByteBuf
private final ByteBuf trackedByteBuf;
/ / DefaultResourceLeak instance
final ResourceLeakTracker<ByteBuf> leak;
SimpleLeakAwareByteBuf(ByteBuf wrapped, ByteBuf trackedByteBuf, ResourceLeakTracker<ByteBuf> leak) {
super(wrapped);
this.trackedByteBuf = ObjectUtil.checkNotNull(trackedByteBuf, "trackedByteBuf");
this.leak = ObjectUtil.checkNotNull(leak, "leak"); }}Copy the code
conclusion
-
How to use Netty leak detection?
- Code: ResourceLeakDetector. SetLevel (ResourceLeakDetector. Level. The PARANOID);
- Configuration: – Dio.net ty. LeakDetection. Level = the paranoid
-
How can I properly configure the Netty leak detection level?
Different leak detection levels correspond to different leak detection behaviors.
level Open detection probability Corresponds to the ByteBuf implementation class DISABLED 0% – SIMPLE The default 1/128 SimpleLeakAwareByteBuf ADVANCED The default 1/128 AdvancedLeakAwareByteBuf PARANOID 100% AdvancedLeakAwareByteBuf By default, the leak detection level is SIMPLE, the sample rate for SIMPLE and ADVANCED is 1/128, and the maximum Record for a single leak place is 4.
-
How to implement Netty leak detection?
- When ByteBuf is created, leak detection is enabled. When the ByteBuf reference count is 0, leak detection is turned off when the release method is executed.
- Only when ByteBuf is created, leak detection is triggered and a leak report is printed.