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(Class<?> resourceType, int samplingInterval, long maxActive) {
209         this(resourceType, samplingInterval);
210     }
211 
212     /**
213      * This should not be used directly by users of {@link ResourceLeakDetector}.
214      * Please use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class)}
215      * or {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}
216      */
217     @SuppressWarnings("deprecation")
218     public ResourceLeakDetector(Class<?> resourceType, int samplingInterval) {
219         this(simpleClassName(resourceType), samplingInterval, Long.MAX_VALUE);
220     }
221 
222     /**
223      * @deprecated use {@link ResourceLeakDetectorFactory#newResourceLeakDetector(Class, int, long)}.
224      * <p>
225      * @param maxActive This is deprecated and will be ignored.
226      */
227     @Deprecated
228     public ResourceLeakDetector(String resourceType, int samplingInterval, long maxActive) {
229         this.resourceType = ObjectUtil.checkNotNull(resourceType, "resourceType");
230         this.samplingInterval = samplingInterval;
231     }
232 
233     /**
234      * Creates a new {@link ResourceLeak} which is expected to be closed via {@link ResourceLeak#close()} when the
235      * related resource is deallocated.
236      *
237      * @return the {@link ResourceLeak} or {@code null}
238      * @deprecated use {@link #track(Object)}
239      */
240     @Deprecated
241     public final ResourceLeak open(T obj) {
242         return track0(obj, false);
243     }
244 
245     /**
246      * Creates a new {@link ResourceLeakTracker} which is expected to be closed via
247      * {@link ResourceLeakTracker#close(Object)} when the related resource is deallocated.
248      *
249      * @return the {@link ResourceLeakTracker} or {@code null}
250      */
251     @SuppressWarnings("unchecked")
252     public final ResourceLeakTracker<T> track(T obj) {
253         return track0(obj, false);
254     }
255 
256     /**
257      * Creates a new {@link ResourceLeakTracker} which is expected to be closed via
258      * {@link ResourceLeakTracker#close(Object)} when the related resource is deallocated.
259      *
260      * Unlike {@link #track(Object)}, this method always returns a tracker, regardless
261      * of the detection settings.
262      *
263      * @return the {@link ResourceLeakTracker}
264      */
265     @SuppressWarnings("unchecked")
266     public ResourceLeakTracker<T> trackForcibly(T obj) {
267         return track0(obj, true);
268     }
269 
270     @SuppressWarnings("unchecked")
271     private DefaultResourceLeak track0(T obj, boolean force) {
272         Level level = ResourceLeakDetector.level;
273         if (force ||
274                 level == Level.PARANOID ||
275                 (level != Level.DISABLED && ThreadLocalRandom.current().nextInt(samplingInterval) == 0)) {
276             reportLeak();
277             return new DefaultResourceLeak(obj, refQueue, allLeaks, getInitialHint(resourceType));
278         }
279         return null;
280     }
281 
282     private void clearRefQueue() {
283         for (;;) {
284             DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
285             if (ref == null) {
286                 break;
287             }
288             ref.dispose();
289         }
290     }
291 
292     /**
293      * When the return value is {@code true}, {@link #reportTracedLeak} and {@link #reportUntracedLeak}
294      * will be called once a leak is detected, otherwise not.
295      *
296      * @return {@code true} to enable leak reporting.
297      */
298     protected boolean needReport() {
299         return logger.isErrorEnabled();
300     }
301 
302     private void reportLeak() {
303         if (!needReport()) {
304             clearRefQueue();
305             return;
306         }
307 
308         // Detect and report previous leaks.
309         for (;;) {
310             DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
311             if (ref == null) {
312                 break;
313             }
314 
315             if (!ref.dispose()) {
316                 continue;
317             }
318 
319             String records = ref.getReportAndClearRecords();
320             if (reportedLeaks.add(records)) {
321                 if (records.isEmpty()) {
322                     reportUntracedLeak(resourceType);
323                 } else {
324                     reportTracedLeak(resourceType, records);
325                 }
326 
327                 LeakListener listener = leakListener;
328                 if (listener != null) {
329                     listener.onLeak(resourceType, records);
330                 }
331             }
332         }
333     }
334 
335     /**
336      * This method is called when a traced leak is detected. It can be overridden for tracking how many times leaks
337      * have been detected.
338      */
339     protected void reportTracedLeak(String resourceType, String records) {
340         logger.error(
341                 "LEAK: {}.release() was not called before it's garbage-collected. " +
342                 "See https://netty.io/wiki/reference-counted-objects.html for more information.{}",
343                 resourceType, records);
344     }
345 
346     /**
347      * This method is called when an untraced leak is detected. It can be overridden for tracking how many times leaks
348      * have been detected.
349      */
350     protected void reportUntracedLeak(String resourceType) {
351         logger.error("LEAK: {}.release() was not called before it's garbage-collected. " +
352                 "Enable advanced leak reporting to find out where the leak occurred. " +
353                 "To enable advanced leak reporting, " +
354                 "specify the JVM option '-D{}={}' or call {}.setLevel() " +
355                 "See https://netty.io/wiki/reference-counted-objects.html for more information.",
356                 resourceType, PROP_LEVEL, Level.ADVANCED.name().toLowerCase(), simpleClassName(this));
357     }
358 
359     /**
360      * @deprecated This method will no longer be invoked by {@link ResourceLeakDetector}.
361      */
362     @Deprecated
363     protected void reportInstancesLeak(String resourceType) {
364     }
365 
366     /**
367      * Create a hint object to be attached to an object tracked by this record. Similar to the additional information
368      * supplied to {@link ResourceLeakTracker#record(Object)}, will be printed alongside the stack trace of the
369      * creation of the resource.
370      */
371     protected Object getInitialHint(String resourceType) {
372         return null;
373     }
374 
375     /**
376      * Set leak listener. Previous listener will be replaced.
377      */
378     public void setLeakListener(LeakListener leakListener) {
379         this.leakListener = leakListener;
380     }
381 
382     public interface LeakListener {
383 
384         /**
385          * Will be called once a leak is detected.
386          */
387         void onLeak(String resourceType, String records);
388     }
389 
390     @SuppressWarnings("deprecation")
391     private static final class DefaultResourceLeak<T>
392             extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
393 
394         @SuppressWarnings("unchecked") // generics and updaters do not mix.
395         private static final AtomicReferenceFieldUpdater<DefaultResourceLeak<?>, TraceRecord> headUpdater =
396                 (AtomicReferenceFieldUpdater)
397                         AtomicReferenceFieldUpdater.newUpdater(DefaultResourceLeak.class, TraceRecord.class, "head");
398 
399         @SuppressWarnings("unchecked") // generics and updaters do not mix.
400         private static final AtomicIntegerFieldUpdater<DefaultResourceLeak<?>> droppedRecordsUpdater =
401                 (AtomicIntegerFieldUpdater)
402                         AtomicIntegerFieldUpdater.newUpdater(DefaultResourceLeak.class, "droppedRecords");
403 
404         @SuppressWarnings("unused")
405         private volatile TraceRecord head;
406         @SuppressWarnings("unused")
407         private volatile int droppedRecords;
408 
409         private final Set<DefaultResourceLeak<?>> allLeaks;
410         private final int trackedHash;
411 
412         DefaultResourceLeak(
413                 Object referent,
414                 ReferenceQueue<Object> refQueue,
415                 Set<DefaultResourceLeak<?>> allLeaks,
416                 Object initialHint) {
417             super(referent, refQueue);
418 
419             assert referent != null;
420 
421             this.allLeaks = allLeaks;
422 
423             // Store the hash of the tracked object to later assert it in the close(...) method.
424             // It's important that we not store a reference to the referent as this would disallow it from
425             // be collected via the WeakReference.
426             trackedHash = System.identityHashCode(referent);
427             allLeaks.add(this);
428             // Create a new Record so we always have the creation stacktrace included.
429             headUpdater.set(this, initialHint == null ?
430                     new TraceRecord(TraceRecord.BOTTOM) : new TraceRecord(TraceRecord.BOTTOM, initialHint));
431         }
432 
433         @Override
434         public void record() {
435             record0(null);
436         }
437 
438         @Override
439         public void record(Object hint) {
440             record0(hint);
441         }
442 
443         /**
444          * This method works by exponentially backing off as more records are present in the stack. Each record has a
445          * 1 / 2^n chance of dropping the top most record and replacing it with itself. This has a number of convenient
446          * properties:
447          *
448          * <ol>
449          * <li>  The current record is always recorded. This is due to the compare and swap dropping the top most
450          *       record, rather than the to-be-pushed record.
451          * <li>  The very last access will always be recorded. This comes as a property of 1.
452          * <li>  It is possible to retain more records than the target, based upon the probability distribution.
453          * <li>  It is easy to keep a precise record of the number of elements in the stack, since each element has to
454          *     know how tall the stack is.
455          * </ol>
456          *
457          * In this particular implementation, there are also some advantages. A thread local random is used to decide
458          * if something should be recorded. This means that if there is a deterministic access pattern, it is now
459          * possible to see what other accesses occur, rather than always dropping them. Second, after
460          * {@link #TARGET_RECORDS} accesses, backoff occurs. This matches typical access patterns,
461          * where there are either a high number of accesses (i.e. a cached buffer), or low (an ephemeral buffer), but
462          * not many in between.
463          *
464          * The use of atomics avoids serializing a high number of accesses, when most of the records will be thrown
465          * away. High contention only happens when there are very few existing records, which is only likely when the
466          * object isn't shared! If this is a problem, the loop can be aborted and the record dropped, because another
467          * thread won the race.
468          */
469         private void record0(Object hint) {
470             // Check TARGET_RECORDS > 0 here to avoid similar check before remove from and add to lastRecords
471             if (TARGET_RECORDS > 0) {
472                 TraceRecord oldHead;
473                 TraceRecord prevHead;
474                 TraceRecord newHead;
475                 boolean dropped;
476                 do {
477                     if ((prevHead = oldHead = headUpdater.get(this)) == null ||
478                             oldHead.pos == TraceRecord.CLOSE_MARK_POS) {
479                         // already closed.
480                         return;
481                     }
482                     final int numElements = oldHead.pos + 1;
483                     if (numElements >= TARGET_RECORDS) {
484                         final int backOffFactor = Math.min(numElements - TARGET_RECORDS, 30);
485                         dropped = ThreadLocalRandom.current().nextInt(1 << backOffFactor) != 0;
486                         if (dropped) {
487                             prevHead = oldHead.next;
488                         }
489                     } else {
490                         dropped = false;
491                     }
492                     newHead = hint != null ? new TraceRecord(prevHead, hint) : new TraceRecord(prevHead);
493                 } while (!headUpdater.compareAndSet(this, oldHead, newHead));
494                 if (dropped) {
495                     droppedRecordsUpdater.incrementAndGet(this);
496                 }
497             }
498         }
499 
500         boolean dispose() {
501             clear();
502             return allLeaks.remove(this);
503         }
504 
505         @Override
506         public boolean close() {
507             if (allLeaks.remove(this)) {
508                 // Call clear so the reference is not even enqueued.
509                 clear();
510                 headUpdater.set(this, TRACK_CLOSE ? new TraceRecord(true) : null);
511                 return true;
512             }
513             return false;
514         }
515 
516         @Override
517         public boolean close(T trackedObject) {
518             // Ensure that the object that was tracked is the same as the one that was passed to close(...).
519             assert trackedHash == System.identityHashCode(trackedObject);
520 
521             try {
522                 return close();
523             } finally {
524                 // This method will do `synchronized(trackedObject)` and we should be sure this will not cause deadlock.
525                 // It should not, because somewhere up the callstack should be a (successful) `trackedObject.release`,
526                 // therefore it is unreasonable that anyone else, anywhere, is holding a lock on the trackedObject.
527                 // (Unreasonable but possible, unfortunately.)
528                 reachabilityFence0(trackedObject);
529             }
530         }
531 
532          /**
533          * Ensures that the object referenced by the given reference remains
534          * <a href="package-summary.html#reachability"><em>strongly reachable</em></a>,
535          * regardless of any prior actions of the program that might otherwise cause
536          * the object to become unreachable; thus, the referenced object is not
537          * reclaimable by garbage collection at least until after the invocation of
538          * this method.
539          *
540          * <p> Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable.
541          * see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8
542          * The Java 9 method Reference.reachabilityFence offers a solution to this problem.
543          *
544          * <p> This method is always implemented as a synchronization on {@code ref}, not as
545          * {@code Reference.reachabilityFence} for consistency across platforms and to allow building on JDK 6-8.
546          * <b>It is the caller's responsibility to ensure that this synchronization will not cause deadlock.</b>
547          *
548          * @param ref the reference. If {@code null}, this method has no effect.
549          * @see java.lang.ref.Reference#reachabilityFence
550          */
551         private static void reachabilityFence0(Object ref) {
552             if (ref != null) {
553                 synchronized (ref) {
554                     // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521
555                 }
556             }
557         }
558 
559         @Override
560         public @Nullable Throwable getCloseStackTraceIfAny() {
561             TraceRecord head = headUpdater.get(this);
562             if (head != null && head.pos == TraceRecord.CLOSE_MARK_POS) {
563                 return head;
564             }
565             return null;
566         }
567 
568         @Override
569         public String toString() {
570             TraceRecord oldHead = headUpdater.get(this);
571             return generateReport(oldHead);
572         }
573 
574         String getReportAndClearRecords() {
575             TraceRecord oldHead = headUpdater.getAndSet(this, null);
576             return generateReport(oldHead);
577         }
578 
579         private String generateReport(TraceRecord oldHead) {
580             if (oldHead == null) {
581                 // Already closed
582                 return EMPTY_STRING;
583             }
584 
585             final int dropped = droppedRecordsUpdater.get(this);
586             int duped = 0;
587 
588             int present = oldHead.pos + 1;
589             // Guess about 2 kilobytes per stack trace
590             StringBuilder buf = new StringBuilder(present * 2048).append(NEWLINE);
591             buf.append("Recent access records: ").append(NEWLINE);
592 
593             int i = 1;
594             Set<String> seen = new HashSet<String>(present);
595             for (; oldHead != TraceRecord.BOTTOM; oldHead = oldHead.next) {
596                 String s = oldHead.toString();
597                 if (seen.add(s)) {
598                     if (oldHead.next == TraceRecord.BOTTOM) {
599                         buf.append("Created at:").append(NEWLINE).append(s);
600                     } else {
601                         buf.append('#').append(i++).append(':').append(NEWLINE).append(s);
602                     }
603                 } else {
604                     duped++;
605                 }
606             }
607 
608             if (duped > 0) {
609                 buf.append(": ")
610                         .append(duped)
611                         .append(" leak records were discarded because they were duplicates")
612                         .append(NEWLINE);
613             }
614 
615             if (dropped > 0) {
616                 buf.append(": ")
617                    .append(dropped)
618                    .append(" leak records were discarded because the leak record count is targeted to ")
619                    .append(TARGET_RECORDS)
620                    .append(". Use system property ")
621                    .append(PROP_TARGET_RECORDS)
622                    .append(" to increase the limit.")
623                    .append(NEWLINE);
624             }
625 
626             buf.setLength(buf.length() - NEWLINE.length());
627             return buf.toString();
628         }
629     }
630 
631     private static final AtomicReference<String[]> excludedMethods =
632             new AtomicReference<String[]>(EmptyArrays.EMPTY_STRINGS);
633 
634     public static void addExclusions(Class clz, String ... methodNames) {
635         Set<String> nameSet = new HashSet<String>(Arrays.asList(methodNames));
636         // Use loop rather than lookup. This avoids knowing the parameters, and doesn't have to handle
637         // NoSuchMethodException.
638         for (Method method : clz.getDeclaredMethods()) {
639             if (nameSet.remove(method.getName()) && nameSet.isEmpty()) {
640                 break;
641             }
642         }
643         if (!nameSet.isEmpty()) {
644             throw new IllegalArgumentException("Can't find '" + nameSet + "' in " + clz.getName());
645         }
646         String[] oldMethods;
647         String[] newMethods;
648         do {
649             oldMethods = excludedMethods.get();
650             newMethods = Arrays.copyOf(oldMethods, oldMethods.length + 2 * methodNames.length);
651             for (int i = 0; i < methodNames.length; i++) {
652                 newMethods[oldMethods.length + i * 2] = clz.getName();
653                 newMethods[oldMethods.length + i * 2 + 1] = methodNames[i];
654             }
655         } while (!excludedMethods.compareAndSet(oldMethods, newMethods));
656     }
657 
658     private static class TraceRecord extends Throwable {
659         private static final long serialVersionUID = 6065153674892850720L;
660         public static final int BOTTOM_POS = -1;
661         public static final int CLOSE_MARK_POS = -2;
662 
663         private static final TraceRecord BOTTOM = new TraceRecord(false) {
664             private static final long serialVersionUID = 7396077602074694571L;
665 
666             // Override fillInStackTrace() so we not populate the backtrace via a native call and so leak the
667             // Classloader.
668             // See https://github.com/netty/netty/pull/10691
669             @Override
670             public Throwable fillInStackTrace() {
671                 return this;
672             }
673         };
674 
675         private final String hintString;
676         private final TraceRecord next;
677         private final int pos;
678 
679         TraceRecord(TraceRecord next, Object hint) {
680             // This needs to be generated even if toString() is never called as it may change later on.
681             hintString = hint instanceof ResourceLeakHint ? ((ResourceLeakHint) hint).toHintString() : hint.toString();
682             this.next = next;
683             this.pos = next.pos + 1;
684         }
685 
686         TraceRecord(TraceRecord next) {
687            hintString = null;
688            this.next = next;
689            this.pos = next.pos + 1;
690         }
691 
692         // Used to terminate the stack
693         private TraceRecord(boolean closeMarker) {
694             hintString = null;
695             next = null;
696             pos = closeMarker ? CLOSE_MARK_POS : BOTTOM_POS;
697         }
698 
699         @Override
700         public String toString() {
701             StringBuilder buf = new StringBuilder(2048);
702             if (hintString != null) {
703                 buf.append("\tHint: ").append(hintString).append(NEWLINE);
704             }
705 
706             // Append the stack trace.
707             StackTraceElement[] array = getStackTrace();
708             // Skip the first three elements.
709             out: for (int i = 3; i < array.length; i++) {
710                 StackTraceElement element = array[i];
711                 // Strip the noisy stack trace elements.
712                 String[] exclusions = excludedMethods.get();
713                 for (int k = 0; k < exclusions.length; k += 2) {
714                     // Suppress a warning about out of bounds access
715                     // since the length of excludedMethods is always even, see addExclusions()
716                     if (exclusions[k].equals(element.getClassName())
717                             && exclusions[k + 1].equals(element.getMethodName())) {
718                         continue out;
719                     }
720                 }
721 
722                 buf.append('\t');
723                 buf.append(element.toString());
724                 buf.append(NEWLINE);
725             }
726             return buf.toString();
727         }
728     }
729 }