Skip to content

Commit 1a11334

Browse files
FlorianKirmaierarapte
authored andcommitted
8244297: Provide utility for testing for memory leaks
Reviewed-by: kcr, arapte
1 parent 0376e60 commit 1a11334

File tree

5 files changed

+357
-98
lines changed

5 files changed

+357
-98
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package test.util.memory;
27+
28+
import com.sun.management.HotSpotDiagnosticMXBean;
29+
import javax.management.MBeanServer;
30+
import java.io.File;
31+
import java.io.IOException;
32+
import java.lang.management.ManagementFactory;
33+
import java.lang.ref.WeakReference;
34+
import java.text.SimpleDateFormat;
35+
import java.util.Date;
36+
import java.util.LinkedList;
37+
import java.util.Objects;
38+
import java.util.function.Consumer;
39+
import java.util.function.Function;
40+
41+
42+
/**
43+
* JMemoryBuddy provides various methods to test for memory leaks.
44+
* It makes it easy to verify the memory behavior in a unit test ensuring the stability and quality of your code.
45+
* Checkout <a href="https://github.com/Sandec/JMemoryBuddy">https://github.com/Sandec/JMemoryBuddy</a> for more documentation.
46+
*/
47+
public class JMemoryBuddy {
48+
49+
private static int steps = 10;
50+
private static int testDuration = 1000;
51+
private static int sleepDuration = testDuration / steps;
52+
private static boolean createHeapdump = false;
53+
private static int garbageAmount = 999999;
54+
private static String mxBeanProxyName = "com.sun.management:type=HotSpotDiagnostic";
55+
private static String outputFolderString = ".";
56+
57+
static {
58+
outputFolderString = System.getProperty("jmemorybuddy.output", getDefaultOutputFolder());
59+
testDuration = Integer.parseInt(System.getProperty("jmemorybuddy.testDuration","1000"));
60+
steps = Integer.parseInt(System.getProperty("jmemorybuddy.steps", "10"));
61+
createHeapdump = Boolean.parseBoolean(System.getProperty("jmemorybuddy.createHeapdump", "false"));
62+
garbageAmount = Integer.parseInt(System.getProperty("jmemorybuddy.garbageAmount", "10"));
63+
}
64+
65+
private static String getDefaultOutputFolder() {
66+
File folder1 = new File("target");
67+
File folder2 = new File("build");
68+
69+
if (folder1.exists()) return folder1.getAbsolutePath();
70+
if (folder2.exists()) return folder2.getAbsolutePath();
71+
return ".";
72+
}
73+
74+
static void createGarbage() {
75+
LinkedList list = new LinkedList<Integer>();
76+
int counter = 0;
77+
while (counter < garbageAmount) {
78+
counter += 1;
79+
list.add(1);
80+
}
81+
}
82+
83+
/**
84+
* Checks whether the content of the WeakReference can be collected.
85+
* @param weakReference The WeakReference to check.
86+
*/
87+
public static void assertCollectable(WeakReference weakReference) {
88+
if (!checkCollectable(weakReference)) {
89+
AssertCollectable assertCollectable = new AssertCollectable(weakReference);
90+
createHeapDump();
91+
throw new AssertionError("Content of WeakReference was not collected. content: " + weakReference.get());
92+
}
93+
}
94+
95+
/**
96+
* Checks whether the content of the WeakReference can be collected.
97+
* @param weakReference The WeakReference to check.
98+
* @return Returns true, when the provided WeakReference can be collected.
99+
*/
100+
public static boolean checkCollectable(WeakReference weakReference) {
101+
return checkCollectable(steps, weakReference) > 0;
102+
}
103+
104+
private static int checkCollectable(int stepsLeft, WeakReference weakReference) {
105+
int counter = stepsLeft;
106+
107+
if (weakReference.get() != null) {
108+
createGarbage();
109+
System.gc();
110+
System.runFinalization();
111+
}
112+
113+
while (counter > 0 && weakReference.get() != null) {
114+
try {
115+
Thread.sleep(sleepDuration);
116+
} catch (InterruptedException e) {
117+
throw new RuntimeException(e);
118+
}
119+
counter = counter - 1;
120+
createGarbage();
121+
System.gc();
122+
System.runFinalization();
123+
}
124+
125+
if (weakReference.get() == null && counter < steps / 3) {
126+
int percentageUsed = (int) ((steps - counter) / steps * 100);
127+
System.out.println("Warning test seems to be unstable. time used: " + percentageUsed + "%");
128+
}
129+
130+
return counter;
131+
}
132+
133+
/**
134+
* Checks whether the content of the WeakReference cannot be collected.
135+
* @param weakReference The WeakReference to check.
136+
*/
137+
public static void assertNotCollectable(WeakReference weakReference) {
138+
if (!checkNotCollectable(weakReference)) {
139+
throw new AssertionError("Content of WeakReference was collected!");
140+
}
141+
}
142+
143+
/**
144+
* Checks whether the content of the WeakReference cannot be collected.
145+
* @param weakReference The WeakReference to check.
146+
* @return Returns true, when the provided WeakReference cannot be collected.
147+
*/
148+
public static boolean checkNotCollectable(WeakReference weakReference) {
149+
createGarbage();
150+
System.gc();
151+
System.runFinalization();
152+
createGarbage();
153+
System.gc();
154+
return weakReference.get() != null;
155+
}
156+
157+
/**
158+
* A standard method to define a test which checks code for specific memory semantic.
159+
* The parameter of the lambda provides an API to define the required memory semantic.
160+
* @param f A function which get's executed with the API to define the required memory semantic.
161+
*/
162+
public static void memoryTest(Consumer<MemoryTestAPI> f) {
163+
LinkedList<WeakReference> toBeCollected = new LinkedList<WeakReference>();
164+
LinkedList<AssertNotCollectable> toBeNotCollected = new LinkedList<AssertNotCollectable>();
165+
LinkedList<SetAsReferenced> toBeReferenced = new LinkedList<SetAsReferenced>();
166+
167+
f.accept(new MemoryTestAPI() {
168+
public void assertCollectable(Object ref) {
169+
Objects.requireNonNull(ref);
170+
toBeCollected.add(new WeakReference<Object>(ref));
171+
}
172+
public void assertNotCollectable(Object ref) {
173+
Objects.requireNonNull(ref);
174+
toBeNotCollected.add(new AssertNotCollectable(ref));
175+
}
176+
public void setAsReferenced(Object ref) {
177+
Objects.requireNonNull(ref);
178+
toBeReferenced.add(new SetAsReferenced(ref));
179+
}
180+
});
181+
182+
int stepsLeft = steps;
183+
boolean failed = false;
184+
185+
for (WeakReference wRef: toBeCollected) {
186+
stepsLeft = checkCollectable(stepsLeft, wRef);
187+
}
188+
if (stepsLeft == 0) {
189+
failed = true;
190+
}
191+
for (AssertNotCollectable wRef: toBeNotCollected) {
192+
if (!checkNotCollectable(wRef.getWeakReference())) {
193+
failed = true;
194+
};
195+
}
196+
197+
if (failed) {
198+
LinkedList<AssertCollectable> toBeCollectedMarked = new LinkedList<AssertCollectable>();
199+
LinkedList<AssertNotCollectable> toBeNotCollectedMarked = new LinkedList<AssertNotCollectable>();
200+
201+
for (WeakReference wRef: toBeCollected) {
202+
if (wRef.get() != null) {
203+
toBeCollectedMarked.add(new AssertCollectable(wRef));
204+
}
205+
}
206+
for (AssertNotCollectable wRef: toBeNotCollected) {
207+
if (wRef.getWeakReference().get() == null) {
208+
toBeNotCollectedMarked.add(wRef);
209+
}
210+
}
211+
createHeapDump();
212+
if (toBeNotCollectedMarked.isEmpty()) {
213+
throw new AssertionError("The following references should be collected: " + toBeCollectedMarked);
214+
} else {
215+
throw new AssertionError("The following references should be collected: " + toBeCollectedMarked + " and " + toBeNotCollected.size() + " should not be collected: " + toBeNotCollectedMarked);
216+
}
217+
}
218+
}
219+
220+
static void createHeapDump() {
221+
if (createHeapdump) {
222+
try {
223+
String dateString = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new Date());
224+
String fileName = "heapdump_jmemb_" + dateString + ".hprof";
225+
File outputFolder = new File(outputFolderString);
226+
String heapdumpFile = new java.io.File(outputFolder, fileName).getAbsolutePath();
227+
System.out.println("Creating Heapdump at: " + heapdumpFile);
228+
getHotspotMBean().dumpHeap(heapdumpFile, true);
229+
} catch (IOException e) {
230+
e.printStackTrace();
231+
}
232+
} else {
233+
System.out.println("No Heapdump was created. You might want to change the configuration to get a HeapDump.");
234+
}
235+
}
236+
237+
private static void setMxBeanProxyName(String mxBeanName) {
238+
mxBeanProxyName = mxBeanName;
239+
}
240+
241+
private static HotSpotDiagnosticMXBean getHotspotMBean() throws IOException {
242+
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
243+
HotSpotDiagnosticMXBean bean =
244+
ManagementFactory.newPlatformMXBeanProxy(server,
245+
mxBeanProxyName, HotSpotDiagnosticMXBean.class);
246+
return bean;
247+
}
248+
249+
/**
250+
* This class provides different methods, which can be used to declare memory-constraints.
251+
* You can get an instance through the lambda of the method JMemoryBuddy.memoryTest.
252+
*/
253+
public static interface MemoryTestAPI {
254+
/**
255+
* After executing the lambda, the provided ref must be collectable. Otherwise an Exception is thrown.
256+
* @param ref The reference which should be collectable.
257+
*/
258+
public void assertCollectable(Object ref);
259+
/**
260+
* After executing the lambda, the provided ref must be not collectable. Otherwise an Exception is thrown.
261+
* @param ref The reference which should not be collectable.
262+
*/
263+
public void assertNotCollectable(Object ref);
264+
265+
/**
266+
* The provided reference will be reference hard, so it won't be collected, until memoryTest finishes.
267+
* @param ref The reference which should get a hard reference for this test.
268+
*/
269+
public void setAsReferenced(Object ref);
270+
}
271+
272+
static class AssertCollectable {
273+
WeakReference<Object> assertCollectable;
274+
275+
AssertCollectable(WeakReference<Object> ref) {
276+
this.assertCollectable = ref;
277+
}
278+
279+
WeakReference<Object> getWeakReference() {
280+
return assertCollectable;
281+
}
282+
283+
@Override
284+
public String toString() {
285+
Object el = assertCollectable.get();
286+
return el != null ? el.toString() : "null";
287+
}
288+
}
289+
290+
private static class AssertNotCollectable {
291+
WeakReference<Object> assertNotCollectable;
292+
String originalResultOfToString;
293+
294+
AssertNotCollectable(Object ref) {
295+
this.assertNotCollectable = new WeakReference<>(ref);
296+
originalResultOfToString = ref.toString();
297+
}
298+
299+
WeakReference<Object> getWeakReference() {
300+
return assertNotCollectable;
301+
}
302+
303+
@Override
304+
public String toString() {
305+
return originalResultOfToString;
306+
}
307+
}
308+
309+
private static class SetAsReferenced {
310+
Object setAsReferenced;
311+
312+
SetAsReferenced(Object ref) {
313+
this.setAsReferenced = ref;
314+
}
315+
}
316+
317+
}

0 commit comments

Comments
 (0)