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 }