View Javadoc
1   /*
2    * Copyright 2013 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.util;
18  
19  import io.netty.util.internal.EmptyArrays;
20  import io.netty.util.internal.ObjectUtil;
21  import io.netty.util.internal.SystemPropertyUtil;
22  import io.netty.util.internal.logging.InternalLogger;
23  import io.netty.util.internal.logging.InternalLoggerFactory;
24  import org.jetbrains.annotations.Nullable;
25  
26  import java.lang.ref.ReferenceQueue;
27  import java.lang.ref.WeakReference;
28  import java.lang.reflect.Method;
29  import java.util.Arrays;
30  import java.util.HashSet;
31  import java.util.Set;
32  import java.util.concurrent.ConcurrentHashMap;
33  import java.util.concurrent.ThreadLocalRandom;
34  import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
35  import java.util.concurrent.atomic.AtomicReference;
36  import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
37  
38  import static io.netty.util.internal.StringUtil.EMPTY_STRING;
39  import static io.netty.util.internal.StringUtil.NEWLINE;
40  import static io.netty.util.internal.StringUtil.simpleClassName;
41  
42  public class ResourceLeakDetector<T> {
43  
44      private static final String PROP_LEVEL_OLD = "io.netty.leakDetectionLevel";
45      private static final String PROP_LEVEL = "io.netty.leakDetection.level";
46      private static final Level DEFAULT_LEVEL = Level.SIMPLE;
47  
48      private static final String PROP_TARGET_RECORDS = "io.netty.leakDetection.targetRecords";
49      private static final int DEFAULT_TARGET_RECORDS = 4;
50  
51      private static final String PROP_SAMPLING_INTERVAL = "io.netty.leakDetection.samplingInterval";
52      // There is a minor performance benefit in TLR if this is a power of 2.
53      private static final int DEFAULT_SAMPLING_INTERVAL = 128;
54  
55      private static final String PROP_TRACK_CLOSE = "io.netty.leakDetection.trackClose";
56      private static final boolean DEFAULT_TRACK_CLOSE = true;
57  
58      private static final int TARGET_RECORDS;
59      static final int SAMPLING_INTERVAL;
60      private static final boolean TRACK_CLOSE;
61  
62      /**
63       * Represents the level of resource leak detection.
64       */
65      public enum Level {
66          /**
67           * Disables resource leak detection.
68           */
69          DISABLED,
70          /**
71           * Enables simplistic sampling resource leak detection which reports there is a leak or not,
72           * at the cost of small overhead (default).
73           */
74          SIMPLE,
75          /**
76           * Enables advanced sampling resource leak detection which reports where the leaked object was accessed
77           * recently at the cost of high overhead.
78           */
79          ADVANCED,
80          /**
81           * Enables paranoid resource leak detection which reports where the leaked object was accessed recently,
82           * at the cost of the highest possible overhead (for testing purposes only).
83           */
84          PARANOID;
85  
86          /**
87           * Returns level based on string value. Accepts also string that represents ordinal number of enum.
88           *
89           * @param levelStr - level string : DISABLED, SIMPLE, ADVANCED, PARANOID. Ignores case.
90           * @return corresponding level or SIMPLE level in case of no match.
91           */
92          static Level parseLevel(String levelStr) {
93              String trimmedLevelStr = levelStr.trim();
94              for (Level l : values()) {
95                  if (trimmedLevelStr.equalsIgnoreCase(l.name()) || trimmedLevelStr.equals(String.valueOf(l.ordinal()))) {
96                      return l;
97                  }
98              }
99              return DEFAULT_LEVEL;
100         }
101     }
102 
103     private static Level level;
104 
105     private static final InternalLogger logger = InternalLoggerFactory.getInstance(ResourceLeakDetector.class);
106 
107     static {
108         final boolean disabled;
109         if (SystemPropertyUtil.get("io.netty.noResourceLeakDetection") != null) {
110             disabled = SystemPropertyUtil.getBoolean("io.netty.noResourceLeakDetection", false);
111             logger.debug("-Dio.netty.noResourceLeakDetection: {}", disabled);
112             logger.warn(
113                     "-Dio.netty.noResourceLeakDetection is deprecated. Use '-D{}={}' instead.",
114                     PROP_LEVEL, Level.DISABLED.name().toLowerCase());
115         } else {
116             disabled = false;
117         }
118 
119         Level defaultLevel = disabled? Level.DISABLED : DEFAULT_LEVEL;
120 
121         // First read old property name
122         String levelStr = SystemPropertyUtil.get(PROP_LEVEL_OLD, defaultLevel.name());
123 
124         // If new property name is present, use it
125         levelStr = SystemPropertyUtil.get(PROP_LEVEL, levelStr);
126         Level level = Level.parseLevel(levelStr);
127 
128         TARGET_RECORDS = SystemPropertyUtil.getInt(PROP_TARGET_RECORDS, DEFAULT_TARGET_RECORDS);
129         SAMPLING_INTERVAL = SystemPropertyUtil.getInt(PROP_SAMPLING_INTERVAL, DEFAULT_SAMPLING_INTERVAL);
130         TRACK_CLOSE = SystemPropertyUtil.getBoolean(PROP_TRACK_CLOSE, DEFAULT_TRACK_CLOSE);
131 
132         ResourceLeakDetector.level = level;
133         if (logger.isDebugEnabled()) {
134             logger.debug("-D{}: {}", PROP_LEVEL, level.name().toLowerCase());
135             logger.debug("-D{}: {}", PROP_TARGET_RECORDS, TARGET_RECORDS);
136         }
137     }
138 
139     /**
140      * @deprecated Use {@link #setLevel(Level)} instead.
141      */
142     @Deprecated
143     public static void setEnabled(boolean enabled) {
144         setLevel(enabled? Level.SIMPLE : Level.DISABLED);
145     }
146 
147     /**
148      * Returns {@code true} if resource leak detection is enabled.
149      */
150     public static boolean isEnabled() {
151         return getLevel().ordinal() > Level.DISABLED.ordinal();
152     }
153 
154     /**
155      * Sets the resource leak detection level.
156      */
157     public static void setLevel(Level level) {
158         ResourceLeakDetector.level = ObjectUtil.checkNotNull(level, "level");
159     }
160 
161     /**
162      * Returns the current resource leak detection level.
163      */
164     public static Level getLevel() {
165         return level;
166     }
167 
168     /** the collection of active resources */
169     private final Set<DefaultResourceLeak<?>> allLeaks = ConcurrentHashMap.newKeySet();
170 
171     private final ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
172     private final Set<String> reportedLeaks = ConcurrentHashMap.newKeySet();
173 
174     private final String resourceType;
175     private final int samplingInterval;
176 
177     /**
178      * Will be notified once a leak is detected.
179      */
180     private volatile LeakListener leakListener;
181 
182     /**
183      * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
184      */
185     @Deprecated
186     public ResourceLeakDetector(Class<?> resourceType) {
187         this(simpleClassName(resourceType));
188     }
189 
190     /**
191      * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
192      */
193     @Deprecated
194     public ResourceLeakDetector(String resourceType) {
195         this(resourceType, DEFAULT_SAMPLING_INTERVAL, Long.MAX_VALUE);
196     }
197 
198     /**
199      * @deprecated Use {@link ResourceLeakDetector#ResourceLeakDetector(Class, int)}.
200      * <p>
201      * This should not be used directly by users of {@link ResourceLeakDetector}.
202      * Please use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class)}
203      * or {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}
204      *
205      * @param maxActive This is deprecated and will be ignored.
206      */
207     @Deprecated
208     public ResourceLeakDetector(
209             Class<?> resourceType, int samplingInterval, @SuppressWarnings("unused") long maxActive) {
210         this(resourceType, samplingInterval);
211     }
212 
213     /**
214      * This should not be used directly by users of {@link ResourceLeakDetector}.
215      * Please use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class)}
216      * or {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}
217      */
218     @SuppressWarnings("deprecation")
219     public ResourceLeakDetector(Class<?> resourceType, int samplingInterval) {
220         this(simpleClassName(resourceType), samplingInterval, Long.MAX_VALUE);
221     }
222 
223     /**
224      * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
225      * <p>
226      * @param maxActive This is deprecated and will be ignored.
227      */
228     @Deprecated
229     public ResourceLeakDetector(
230             String resourceType, int samplingInterval, @SuppressWarnings("unused") long maxActive) {
231         this.resourceType = ObjectUtil.checkNotNull(resourceType, "resourceType");
232         this.samplingInterval = samplingInterval;
233     }
234 
235     /**
236      * Creates a new {@link ResourceLeak} which is expected to be closed via {@link ResourceLeak#close()} when the
237      * related resource is deallocated.
238      *
239      * @return the {@link ResourceLeak} or {@code null}
240      * @deprecated use {@link #track(Object)}
241      */
242     @Deprecated
243     public final ResourceLeak open(T obj) {
244         return track0(obj, false);
245     }
246 
247     /**
248      * Creates a new {@link ResourceLeakTracker} which is expected to be closed via
249      * {@link ResourceLeakTracker#close(Object)} when the related resource is deallocated.
250      *
251      * @return the {@link ResourceLeakTracker} or {@code null}
252      */
253     public ResourceLeakTracker<T> track(T obj) {
254         return track0(obj, false);
255     }
256 
257     /**
258      * Creates a new {@link ResourceLeakTracker} which is expected to be closed via
259      * {@link ResourceLeakTracker#close(Object)} when the related resource is deallocated.
260      * <p>
261      * Unlike {@link #track(Object)}, this method always returns a tracker, regardless
262      * of the detection settings.
263      *
264      * @return the {@link ResourceLeakTracker}
265      */
266     public ResourceLeakTracker<T> trackForcibly(T obj) {
267         return track0(obj, true);
268     }
269 
270     /**
271      * Check whether {@link ResourceLeakTracker#record()} does anything for this detector.
272      *
273      * @return {@code true} if {@link ResourceLeakTracker#record()} should be called
274      */
275     public boolean isRecordEnabled() {
276         Level level = getLevel();
277         return (level == Level.ADVANCED || level == Level.PARANOID) && TARGET_RECORDS > 0;
278     }
279 
280     private DefaultResourceLeak<T> track0(T obj, boolean force) {
281         Level level = ResourceLeakDetector.level;
282         if (force ||
283                 level == Level.PARANOID ||
284                 (level != Level.DISABLED && ThreadLocalRandom.current().nextInt(samplingInterval) == 0)) {
285             reportLeak();
286             return new DefaultResourceLeak<>(obj, refQueue, allLeaks, getInitialHint(resourceType));
287         }
288         return null;
289     }
290 
291     private void clearRefQueue() {
292         for (;;) {
293             DefaultResourceLeak<?> ref = (DefaultResourceLeak<?>) refQueue.poll();
294             if (ref == null) {
295                 break;
296             }
297             ref.dispose();
298         }
299     }
300 
301     /**
302      * When the return value is {@code true}, {@link #reportTracedLeak} and {@link #reportUntracedLeak}
303      * will be called once a leak is detected, otherwise not.
304      *
305      * @return {@code true} to enable leak reporting.
306      */
307     protected boolean needReport() {
308         return logger.isErrorEnabled();
309     }
310 
311     private void reportLeak() {
312         if (!needReport()) {
313             clearRefQueue();
314             return;
315         }
316 
317         // Detect and report previous leaks.
318         for (;;) {
319             DefaultResourceLeak<?> ref = (DefaultResourceLeak<?>) refQueue.poll();
320             if (ref == null) {
321                 break;
322             }
323 
324             if (!ref.dispose()) {
325                 continue;
326             }
327 
328             String records = ref.getReportAndClearRecords();
329             if (reportedLeaks.add(records)) {
330                 if (records.isEmpty()) {
331                     reportUntracedLeak(resourceType);
332                 } else {
333                     reportTracedLeak(resourceType, records);
334                 }
335 
336                 LeakListener listener = leakListener;
337                 if (listener != null) {
338                     listener.onLeak(resourceType, records);
339                 }
340             }
341         }
342     }
343 
344     /**
345      * This method is called when a traced leak is detected. It can be overridden for tracking how many times leaks
346      * have been detected.
347      */
348     protected void reportTracedLeak(String resourceType, String records) {
349         logger.error(
350                 "LEAK: {}.release() was not called before it's garbage-collected. " +
351                 "See https://netty.io/wiki/reference-counted-objects.html for more information.{}",
352                 resourceType, records);
353     }
354 
355     /**
356      * This method is called when an untraced leak is detected. It can be overridden for tracking how many times leaks
357      * have been detected.
358      */
359     protected void reportUntracedLeak(String resourceType) {
360         logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
361                 "Enable advanced leak reporting to find out where the leak occurred. " +
362                 "To enable advanced leak reporting, " +
363                 "specify the JVM option '-D{}={}' or call {}.setLevel() " +
364                 "See https://netty.io/wiki/reference-counted-objects.html for more information.",
365                 resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
366     }
367 
368     /**
369      * @deprecated This method will no longer be invoked by {@link ResourceLeakDetector}.
370      */
371     @Deprecated
372     protected void reportInstancesLeak(String resourceType) {
373     }
374 
375     /**
376      * Create a hint object to be attached to an object tracked by this record. Similar to the additional information
377      * supplied to {@link ResourceLeakTracker#record(Object)}, will be printed alongside the stack trace of the
378      * creation of the resource.
379      */
380     protected Object getInitialHint(String resourceType) {
381         return null;
382     }
383 
384     /**
385      * Set leak listener. Previous listener will be replaced.
386      */
387     public void setLeakListener(LeakListener leakListener) {
388         this.leakListener = leakListener;
389     }
390 
391     public interface LeakListener {
392 
393         /**
394          * Will be called once a leak is detected.
395          */
396         void onLeak(String resourceType, String records);
397     }
398 
399     @SuppressWarnings("deprecation")
400     private static final class DefaultResourceLeak<T>
401             extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
402 
403         @SuppressWarnings({"unchecked", "rawtypes"}) // generics and updaters do not mix.
404         private static final AtomicReferenceFieldUpdater<DefaultResourceLeak<?>, TraceRecord> headUpdater =
405                 (AtomicReferenceFieldUpdater)
406                         AtomicReferenceFieldUpdater.newUpdater(DefaultResourceLeak.class, TraceRecord.class, "head");
407 
408         @SuppressWarnings({"unchecked", "rawtypes"}) // generics and updaters do not mix.
409         private static final AtomicIntegerFieldUpdater<DefaultResourceLeak<?>> droppedRecordsUpdater =
410                 (AtomicIntegerFieldUpdater)
411                         AtomicIntegerFieldUpdater.newUpdater(DefaultResourceLeak.class, "droppedRecords");
412 
413         @SuppressWarnings("unused")
414         private volatile TraceRecord head;
415         @SuppressWarnings("unused")
416         private volatile int droppedRecords;
417 
418         private final Set<DefaultResourceLeak<?>> allLeaks;
419         private final int trackedHash;
420 
421         DefaultResourceLeak(
422                 Object referent,
423                 ReferenceQueue<Object> refQueue,
424                 Set<DefaultResourceLeak<?>> allLeaks,
425                 Object initialHint) {
426             super(referent, refQueue);
427 
428             assert referent != null;
429 
430             this.allLeaks = allLeaks;
431 
432             // Store the hash of the tracked object to later assert it in the close(...) method.
433             // It's important that we not store a reference to the referent as this would disallow it from
434             // be collected via the WeakReference.
435             trackedHash = System.identityHashCode(referent);
436             allLeaks.add(this);
437             // Create a new Record so we always have the creation stacktrace included.
438             headUpdater.set(this, initialHint == null ?
439                     new TraceRecord(TraceRecord.BOTTOM) : new TraceRecord(TraceRecord.BOTTOM, initialHint));
440         }
441 
442         @Override
443         public void record() {
444             record0(null);
445         }
446 
447         @Override
448         public void record(Object hint) {
449             record0(hint);
450         }
451 
452         /**
453          * This method works by exponentially backing off as more records are present in the stack. Each record has a
454          * 1 / 2^n chance of dropping the top most record and replacing it with itself. This has a number of convenient
455          * properties:
456          *
457          * <ol>
458          * <li>  The current record is always recorded. This is due to the compare and swap dropping the top most
459          *       record, rather than the to-be-pushed record.
460          * <li>  The very last access will always be recorded. This comes as a property of 1.
461          * <li>  It is possible to retain more records than the target, based upon the probability distribution.
462          * <li>  It is easy to keep a precise record of the number of elements in the stack, since each element has to
463          *     know how tall the stack is.
464          * </ol>
465          *
466          * In this particular implementation, there are also some advantages. A thread local random is used to decide
467          * if something should be recorded. This means that if there is a deterministic access pattern, it is now
468          * possible to see what other accesses occur, rather than always dropping them. Second, after
469          * {@link #TARGET_RECORDS} accesses, backoff occurs. This matches typical access patterns,
470          * where there are either a high number of accesses (i.e. a cached buffer), or low (an ephemeral buffer), but
471          * not many in between.
472          * <p>
473          * The use of atomics avoids serializing a high number of accesses, when most of the records will be thrown
474          * away. High contention only happens when there are very few existing records, which is only likely when the
475          * object isn't shared! If this is a problem, the loop can be aborted and the record dropped, because another
476          * thread won the race.
477          */
478         private void record0(Object hint) {
479             // Check TARGET_RECORDS > 0 here to avoid similar check before remove from and add to lastRecords
480             if (TARGET_RECORDS > 0) {
481                 TraceRecord oldHead;
482                 TraceRecord prevHead;
483                 TraceRecord newHead;
484                 boolean dropped;
485                 do {
486                     if ((prevHead = oldHead = headUpdater.get(this)) == null ||
487                             oldHead.pos == TraceRecord.CLOSE_MARK_POS) {
488                         // already closed.
489                         return;
490                     }
491                     final int numElements = oldHead.pos + 1;
492                     if (numElements >= TARGET_RECORDS) {
493                         final int backOffFactor = Math.min(numElements - TARGET_RECORDS, 30);
494                         dropped = ThreadLocalRandom.current().nextInt(1 << backOffFactor) != 0;
495                         if (dropped) {
496                             prevHead = oldHead.next;
497                         }
498                     } else {
499                         dropped = false;
500                     }
501                     newHead = hint != null ? new TraceRecord(prevHead, hint) : new TraceRecord(prevHead);
502                 } while (!headUpdater.compareAndSet(this, oldHead, newHead));
503                 if (dropped) {
504                     droppedRecordsUpdater.incrementAndGet(this);
505                 }
506             }
507         }
508 
509         boolean dispose() {
510             clear();
511             return allLeaks.remove(this);
512         }
513 
514         @Override
515         public boolean close() {
516             if (allLeaks.remove(this)) {
517                 // Call clear so the reference is not even enqueued.
518                 clear();
519                 headUpdater.set(this, TRACK_CLOSE ? new TraceRecord(true) : null);
520                 return true;
521             }
522             return false;
523         }
524 
525         @Override
526         public boolean close(T trackedObject) {
527             // Ensure that the object that was tracked is the same as the one that was passed to close(...).
528             assert trackedHash == System.identityHashCode(trackedObject);
529 
530             try {
531                 return close();
532             } finally {
533                 // This method will do `synchronized(trackedObject)` and we should be sure this will not cause deadlock.
534                 // It should not, because somewhere up the callstack should be a (successful) `trackedObject.release`,
535                 // therefore it is unreasonable that anyone else, anywhere, is holding a lock on the trackedObject.
536                 // (Unreasonable but possible, unfortunately.)
537                 reachabilityFence0(trackedObject);
538             }
539         }
540 
541          /**
542          * Ensures that the object referenced by the given reference remains
543          * <a href="package-summary.html#reachability"><em>strongly reachable</em></a>,
544          * regardless of any prior actions of the program that might otherwise cause
545          * the object to become unreachable; thus, the referenced object is not
546          * reclaimable by garbage collection at least until after the invocation of
547          * this method.
548          *
549          * <p> Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable.
550          * see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8
551          * The Java 9 method Reference.reachabilityFence offers a solution to this problem.
552          *
553          * <p> This method is always implemented as a synchronization on {@code ref}, not as
554          * {@code Reference.reachabilityFence} for consistency across platforms and to allow building on JDK 6-8.
555          * <b>It is the caller's responsibility to ensure that this synchronization will not cause deadlock.</b>
556          *
557          * @param ref the reference. If {@code null}, this method has no effect.
558          * @see java.lang.ref.Reference#reachabilityFence
559          */
560         @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "EmptySynchronizedStatement"})
561         private static void reachabilityFence0(Object ref) {
562             if (ref != null) {
563                 synchronized (ref) {
564                     // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521
565                 }
566             }
567         }
568 
569         @Override
570         public @Nullable Throwable getCloseStackTraceIfAny() {
571             TraceRecord head = headUpdater.get(this);
572             if (head != null && head.pos == TraceRecord.CLOSE_MARK_POS) {
573                 return head;
574             }
575             return null;
576         }
577 
578         @Override
579         public String toString() {
580             TraceRecord oldHead = headUpdater.get(this);
581             return generateReport(oldHead);
582         }
583 
584         String getReportAndClearRecords() {
585             TraceRecord oldHead = headUpdater.getAndSet(this, null);
586             return generateReport(oldHead);
587         }
588 
589         private String generateReport(TraceRecord oldHead) {
590             if (oldHead == null) {
591                 // Already closed
592                 return EMPTY_STRING;
593             }
594 
595             final int dropped = droppedRecordsUpdater.get(this);
596             int duped = 0;
597 
598             int present = oldHead.pos + 1;
599             // Guess about 2 kilobytes per stack trace
600             StringBuilder buf = new StringBuilder(present * 2048).append(NEWLINE);
601             buf.append("Recent access records: ").append(NEWLINE);
602 
603             int i = 1;
604             Set<String> seen = new HashSet<>(present);
605             for (; oldHead != TraceRecord.BOTTOM; oldHead = oldHead.next) {
606                 String s = oldHead.toString();
607                 if (seen.add(s)) {
608                     if (oldHead.next == TraceRecord.BOTTOM) {
609                         buf.append("Created at:").append(NEWLINE).append(s);
610                     } else {
611                         buf.append('#').append(i++).append(':').append(NEWLINE).append(s);
612                     }
613                 } else {
614                     duped++;
615                 }
616             }
617 
618             if (duped > 0) {
619                 buf.append(": ")
620                         .append(duped)
621                         .append(" leak records were discarded because they were duplicates")
622                         .append(NEWLINE);
623             }
624 
625             if (dropped > 0) {
626                 buf.append(": ")
627                    .append(dropped)
628                    .append(" leak records were discarded because the leak record count is targeted to ")
629                    .append(TARGET_RECORDS)
630                    .append(". Use system property ")
631                    .append(PROP_TARGET_RECORDS)
632                    .append(" to increase the limit.")
633                    .append(NEWLINE);
634             }
635 
636             buf.setLength(buf.length() - NEWLINE.length());
637             return buf.toString();
638         }
639     }
640 
641     private static final AtomicReference<String[]> excludedMethods =
642             new AtomicReference<>(EmptyArrays.EMPTY_STRINGS);
643 
644     public static void addExclusions(@SuppressWarnings("rawtypes") Class clz, String ... methodNames) {
645         Set<String> nameSet = new HashSet<>(Arrays.asList(methodNames));
646         // Use loop rather than lookup. This avoids knowing the parameters, and doesn't have to handle
647         // NoSuchMethodException.
648         for (Method method : clz.getDeclaredMethods()) {
649             if (nameSet.remove(method.getName()) && nameSet.isEmpty()) {
650                 break;
651             }
652         }
653         if (!nameSet.isEmpty()) {
654             throw new IllegalArgumentException("Can't find '" + nameSet + "' in " + clz.getName());
655         }
656         String[] oldMethods;
657         String[] newMethods;
658         do {
659             oldMethods = excludedMethods.get();
660             newMethods = Arrays.copyOf(oldMethods, oldMethods.length + 2 * methodNames.length);
661             for (int i = 0; i < methodNames.length; i++) {
662                 newMethods[oldMethods.length + i * 2] = clz.getName();
663                 newMethods[oldMethods.length + i * 2 + 1] = methodNames[i];
664             }
665         } while (!excludedMethods.compareAndSet(oldMethods, newMethods));
666     }
667 
668     private static class TraceRecord extends Throwable {
669         private static final long serialVersionUID = 6065153674892850720L;
670         public static final int BOTTOM_POS = -1;
671         public static final int CLOSE_MARK_POS = -2;
672 
673         private static final TraceRecord BOTTOM = new TraceRecord(false) {
674             private static final long serialVersionUID = 7396077602074694571L;
675 
676             // Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
677             // Classloader.
678             // See https://github.com/netty/netty/pull/10691
679             @Override
680             public Throwable fillInStackTrace() {
681                 return this;
682             }
683         };
684 
685         private final String hintString;
686         private final TraceRecord next;
687         private final int pos;
688 
689         TraceRecord(TraceRecord next, Object hint) {
690             // This needs to be generated even if toString() is never called as it may change later on.
691             hintString = hint instanceof ResourceLeakHint ? ((ResourceLeakHint) hint).toHintString() : hint.toString();
692             this.next = next;
693             this.pos = next.pos + 1;
694         }
695 
696         TraceRecord(TraceRecord next) {
697            hintString = null;
698            this.next = next;
699            this.pos = next.pos + 1;
700         }
701 
702         // Used to terminate the stack
703         private TraceRecord(boolean closeMarker) {
704             hintString = null;
705             next = null;
706             pos = closeMarker ? CLOSE_MARK_POS : BOTTOM_POS;
707         }
708 
709         @Override
710         public String toString() {
711             StringBuilder buf = new StringBuilder(2048);
712             if (hintString != null) {
713                 buf.append("\tHint: ").append(hintString).append(NEWLINE);
714             }
715 
716             // Append the stack trace.
717             StackTraceElement[] array = getStackTrace();
718             // Skip the first three elements.
719             out: for (int i = 3; i < array.length; i++) {
720                 StackTraceElement element = array[i];
721                 // Strip the noisy stack trace elements.
722                 String[] exclusions = excludedMethods.get();
723                 for (int k = 0; k < exclusions.length; k += 2) {
724                     // Suppress a warning about out of bounds access
725                     // since the length of excludedMethods is always even, see addExclusions()
726                     if (exclusions[k].equals(element.getClassName())
727                             && exclusions[k + 1].equals(element.getMethodName())) {
728                         continue out;
729                     }
730                 }
731 
732                 buf.append('\t');
733                 buf.append(element.toString());
734                 buf.append(NEWLINE);
735             }
736             return buf.toString();
737         }
738     }
739 }