View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.util;
16  
17  import java.lang.ref.WeakReference;
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  /**
22   * <p>A memory leak detector which can be used during testing.</p>
23   *
24   * <p>Based on <a href="http://blogs.ilog.com/jviews/2008/05/05/unit-testing-memory-leaks/">this</a>.</p>
25   *
26   * @author Daniel Gredler
27   */
28  public class MemoryLeakDetector {
29  
30      /** Weak references to the objects being tracked by the detector. */
31      private Map<String, WeakReference<Object>> map_ = new HashMap<>();
32  
33      /**
34       * Registers the specified object with the memory leak detector. Once an object has been registered
35       * with the detector via this method, {@link #canBeGCed(String)} may be called with the specified
36       * ID in order to find out whether or not the registered object can be garbage collected.
37       *
38       * @param id the unique ID under which to register the specified object
39       * @param o the object to be tracked
40       */
41      public void register(final String id, final Object o) {
42          if (map_.containsKey(id)) {
43              throw new IllegalArgumentException("There is already an object registered with ID '" + id + "': " + o);
44          }
45          map_.put(id, new WeakReference<>(o));
46      }
47  
48      /**
49       * Returns {@code true} if the object registered with the specified ID can be garbage collected.
50       *
51       * @param id the ID corresponding to the object to check
52       * @return {@code true} if the object registered with the specified ID can be garbage collected
53       */
54      public boolean canBeGCed(final String id) {
55          final WeakReference<Object> ref = map_.get(id);
56          if (ref == null) {
57              throw new IllegalArgumentException("No object registered with ID '" + id + "'.");
58          }
59          gc();
60          return ref.get() == null;
61      }
62  
63      /**
64       * Does a best effort at getting unreferenced objects garbage collected.
65       */
66      private static void gc() {
67          final Runtime rt = Runtime.getRuntime();
68          for (int i = 0; i < 3; i++) {
69              allocateMemory((int) 2e6);
70              for (int j = 0; j < 3; j++) {
71                  rt.gc();
72              }
73          }
74          rt.runFinalization();
75          try {
76              Thread.sleep(50);
77          }
78          catch (final InterruptedException e) {
79              e.printStackTrace();
80          }
81      }
82  
83      /**
84       * Allocates a byte array of the specified size and messes around with it just to make sure
85       * that the allocation doesn't get optimized away.
86       *
87       * @param bytes the size of the byte array to create
88       * @return the total allocated
89       */
90      private static int allocateMemory(final int bytes) {
91          final byte[] big = new byte[bytes];
92          // Fight against clever compilers/JVMs that may not allocate
93          // unless we actually use the elements of the array.
94          int total = 0;
95          for (int i = 0; i < 10; i++) {
96              // We don't touch all the elements, would take too long.
97              if (i % 2 == 0) {
98                  total += big[i];
99              }
100             else {
101                 total -= big[i];
102             }
103         }
104         return total;
105     }
106 
107 }