/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.core.system;
import java.util.EnumSet;
/**
* Checks for memory leaks.
*
* @author John Mazzitelli
*/
public class MemoryLeakChecker {
public static final EnumSet<LeakType> JAVA_ONLY = EnumSet.of(LeakType.JAVA);
public static final EnumSet<LeakType> NATIVE_ONLY = EnumSet.of(LeakType.NATIVE);
public static final EnumSet<LeakType> JAVA_AND_NATIVE = EnumSet.of(LeakType.JAVA, LeakType.NATIVE);
public enum LeakType {
/** test leaks in Java heap */
JAVA,
/** test for leaks in native code */
NATIVE
}
private static final long ALLOWED_TO_LEAK = Long.parseLong(System
.getProperty("rhq.testng.allowedToLeak", "1000000"));
/**
* Runs the given task alot of times and checks to see if we leak memory.
*
* @param task
* @param message an error message to be included in assert exception if a memory leak is detected
* @param maxLoop maximum outer loop - the number of times the inner task loop is run
* @param taskLoop number of times the task it run in succession (this is the "inner loop")
* @param leakType where to look for leaks - in java heap, in native code or test for both
*
* @throws RuntimeException
*/
public static void checkForMemoryLeak(Runnable task, String message, long maxLoop, long taskLoop,
EnumSet<LeakType> leakType) {
if (leakType.contains(LeakType.JAVA)) {
checkForMemoryLeak(task, message, maxLoop, taskLoop);
}
if (leakType.contains(LeakType.NATIVE)) {
NativeMemoryLeakChecker.checkForMemoryLeak(task, message, maxLoop, taskLoop);
}
}
/**
* Runs the given task alot of times and checks to see if we leak memory in the {@link LeakType#JAVA Java heap}.
*
* @param task
* @param message an error message to be included in assert exception if a memory leak is detected
* @param maxLoop maximum outer loop - the number of times the inner task loop is run
* @param taskLoop number of times the task it run in succession (this is the "inner loop")
*
* @throws RuntimeException
*/
private static void checkForMemoryLeak(Runnable task, String message, long maxLoop, long taskLoop) {
System.out.println("Checking for java heap memory leak [" + message + "]....");
// run the task once - to load in initial memory so first memory usage numbers aren't skewed
task.run();
long startUsedMemory = -1;
long maxLoopCounter = maxLoop;
try {
int endedUpWithLessUsedThanBefore = 0; // number of times afterUsedMemory is less than beforeUsedMemory
long lowestAfterUsedMemoryWhenEndingUpWithLess = Long.MAX_VALUE; // when after was less than before, this is the lower "after" value
while (maxLoopCounter-- > 0) {
long beforeUsedMemory = getUsedMemory();
for (int i = 0; i < taskLoop; i++) {
task.run();
}
long afterUsedMemory = getUsedMemory();
if (startUsedMemory == -1) {
// we'll assign this after the first iteration - so we can get what we think will be steady state memory usage
startUsedMemory = afterUsedMemory;
}
System.out.println("Memory used before/after: " + beforeUsedMemory + '/' + afterUsedMemory);
if (afterUsedMemory <= beforeUsedMemory) {
// hmm we are using the same or less memory now than before - must have GC'ed some memory
// we still might be leaking though - GC might only have cleaned up some but not all
if (lowestAfterUsedMemoryWhenEndingUpWithLess >= afterUsedMemory) {
lowestAfterUsedMemoryWhenEndingUpWithLess = afterUsedMemory;
endedUpWithLessUsedThanBefore++; // memory usage keeps going down, this is good and we might be able to skip the test
}
if (endedUpWithLessUsedThanBefore > (maxLoop / 2)) {
break; // more than half the times we used less memory than we were using at the beginning - no leak here
}
}
}
} catch (OutOfMemoryError oom) {
System.out.println("!!!!! OUT OF MEMORY !!!!! : " + message);
throw oom;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
System.out.println();
}
if (maxLoopCounter <= 0) {
// possible leak - our used memory after the tests was always more than before the test
// see how much memory we used since the start
long endUsedMemory = getUsedMemory();
long usedMemoryDiff = endUsedMemory - startUsedMemory;
if (usedMemoryDiff >= ALLOWED_TO_LEAK) {
System.out.println("[" + message + "] We leaked too much memory: (end-start=diff)->" + endUsedMemory
+ '-' + startUsedMemory + '=' + usedMemoryDiff);
System.out.println("[" + message + "] Going to force a System.gc() to see if we can free some memory");
System.gc();
endUsedMemory = getUsedMemory();
usedMemoryDiff = endUsedMemory - startUsedMemory;
System.out.println("[" + message + "] After System.gc(): (end-start=diff)->" + endUsedMemory + '-'
+ startUsedMemory + '=' + usedMemoryDiff);
assert (usedMemoryDiff < ALLOWED_TO_LEAK) : "[" + message
+ "] We leaked too much java memory: (end-start=diff)->" + endUsedMemory + '-' + startUsedMemory + '='
+ (((float)usedMemoryDiff) / 1024f / 1024f) + "MB";
} else {
System.out.println("[" + message
+ "] Went the distance but did not seem to leak memory: (end-start=diff)->" + endUsedMemory + '-'
+ startUsedMemory + '=' + usedMemoryDiff);
}
}
}
/**
* Returns the amount of used memory for this VM.
*
* @return amount of total memory minus free memory
*/
public static long getUsedMemory() {
long freeMemory = Runtime.getRuntime().freeMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
long usedMemory = totalMemory - freeMemory;
return usedMemory;
}
}