Автоматическое обнаружение утечек памяти в Java - PullRequest
15 голосов
/ 19 июля 2011

Я думал об автоматическом обнаружении утечки памяти для Java-программы.Основной алгоритм состоит в создании JUnits, которые содержат следующую логику:

Call System.gc() several times
Determine initial heap memory consumption using either Runtime class or JMX
Loop 
    Do something that exercises program under test
End loop

Call System.gc() several times
Determine final heap memory consumption
Compare initial and final memory numbers

Цикл используется, чтобы видеть, медленно ли увеличивается память.

Необходимо различатьожидаемое и неожиданное увеличение использования памяти.

Это не совсем модульный тест.Но фреймворк JUnit удобно использовать.

Считаете ли вы, что этот подход действителен?Считаете ли вы, что этот подход будет успешным в выявлении утечек памяти?Вы когда-нибудь делали что-то подобное?

Ответы [ 4 ]

18 голосов
/ 14 сентября 2011

Я разработал простую структуру модульного тестирования для утечек памяти, которая надежно работала для меня. Основная идея состоит в том, чтобы создать слабую ссылку на объект, который должен быть собран мусором, выполнить тест, выполнить полный сборщик мусора, а затем убедиться, что слабая ссылка была очищена.

Вот довольно типичный регрессионный тест с использованием моей платформы:

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");
}

Здесь нужно отметить несколько вещей:

  1. Очень важно, чтобы объект, который вы хотите получить, не был сохранен в переменной в вашем модульном тесте, поскольку он будет сохраняться до конца вашего теста.
  2. Это полезный метод для регрессионных тестов, когда сообщается об утечке, и вы знаете, какой объект должен был быть удален.
  3. Одной из проблем этого подхода является то, что трудно определить, почему тест не удался. На этом этапе вам понадобится профилировщик памяти (я неравнодушен к YourKit). Однако IMO все еще полезно иметь регрессионные тесты, чтобы утечки не могли быть случайно повторно введены в будущем.
  4. Я столкнулся с некоторыми проблемами многопоточности, при которых не все ссылки очищались сразу, поэтому метод теперь пытается выполнить 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());
}

}

7 голосов
/ 19 июля 2011

Вы не можете сделать это с Java. Сборщик мусора запустится, когда определит, что это необходимо. Кроме того, он может «освободить» память, чтобы ее можно было использовать повторно, но это не значит, что она освободит блок.

4 голосов
/ 19 июля 2011

Это не имеет смысла в Java. System.gc() не дает вам никаких разумных гарантий, и даже если вы убедите себя в наличии проблемы, этот подход не поможет вам найти эту проблему.

Вместо этого вы должны использовать профилировщик памяти. Если вы не хотите платить за профессиональный, вы можете попробовать jvisualvm, который устанавливается вместе с JVM.

0 голосов
/ 19 июля 2011

По крайней мере, вы должны сначала выполнить тест, прежде чем выполнять тест, чтобы выбрать одноразовые объекты, которые должны быть созданы только один раз.Так же:

test()
checkMem()
test()
checkMem()
compareIfMemUsageHasNotIncreased()
...