Я разработал простую структуру модульного тестирования для утечек памяти, которая надежно работала для меня. Основная идея состоит в том, чтобы создать слабую ссылку на объект, который должен быть собран мусором, выполнить тест, выполнить полный сборщик мусора, а затем убедиться, что слабая ссылка была очищена.
Вот довольно типичный регрессионный тест с использованием моей платформы:
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());
}
}