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