我为内存泄漏开发了一个简单的单元测试框架,它为我可靠地工作。基本思想是创建一个对应该被垃圾回收的对象的弱引用,执行测试,执行完整的GC,然后验证弱引用是否已被清除。
这是一个相当典型的回归测试,使用我的框架:
public void testDS00032554() throws Exception {
Project testProject = getTestProject();
MemoryLeakVerifier verifier = new MemoryLeakVerifier(new RuntimeTestAction(getTestClassMap()));
testProject.close();
verifier.assertGarbageCollected("RuntimeTestAction should be garbage collected when project closed");
}
这里有一些注意事项:
- 至关重要的是,您希望收集的对象不应存储在单元测试的变量中,因为它将保留到测试结束时。
- 这是一种有用的回归测试技术,其中已报告泄漏,并且您知道应该删除哪个对象。
- 这种方法的一个问题是很难确定测试失败的原因。此时,您将需要一个内存分析器(我偏爱 YourKit)。但是,IMO进行回归测试仍然很有用,这样将来就不会意外地重新引入泄漏。
- 我遇到了一些线程问题,并非所有引用都会立即清除,因此该方法现在尝试在失败之前多次执行GC(如本文所述:Java提示130:您知道您的数据大小吗?)
以下是完整的帮助程序类,以防您想试用它:
/**
* A simple utility class that can verify that an object has been successfully garbage collected.
*/
public class MemoryLeakVerifier {
private static final int MAX_GC_ITERATIONS = 50;
private static final int GC_SLEEP_TIME = 100;
private final WeakReference reference;
public MemoryLeakVerifier(Object object) {
this.reference = new WeakReference(object);
}
public Object getObject() {
return reference.get();
}
/**
* Attempts to perform a full garbage collection so that all weak references will be removed. Usually only
* a single GC is required, but there have been situations where some unused memory is not cleared up on the
* first pass. This method performs a full garbage collection and then validates that the weak reference
* now has been cleared. If it hasn't then the thread will sleep for 50 milliseconds and then retry up to
* 10 more times. If after this the object still has not been collected then the assertion will fail.
*
* Based upon the method described in: http://www.javaworld.com/javaworld/javatips/jw-javatip130.html
*/
public void assertGarbageCollected(String name) {
Runtime runtime = Runtime.getRuntime();
for (int i = 0; i < MAX_GC_ITERATIONS; i++) {
runtime.runFinalization();
runtime.gc();
if (getObject() == null)
break;
// Pause for a while and then go back around the loop to try again...
try {
EventQueue.invokeAndWait(Procedure.NoOp); // Wait for the AWT event queue to have completed processing
Thread.sleep(GC_SLEEP_TIME);
} catch (InterruptedException e) {
// Ignore any interrupts and just try again...
} catch (InvocationTargetException e) {
// Ignore any interrupts and just try again...
}
}
PanteroTestCase.assertNull(name + ": object should not exist after " + MAX_GC_ITERATIONS + " collections", getObject());
}
}