/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.node;
import freenet.support.Logger;
import freenet.support.SizeUtil;
import freenet.support.Ticker;
import freenet.support.Logger.LogLevel;
import freenet.support.math.RunningAverage;
import freenet.support.math.SimpleRunningAverage;
public class MemoryChecker implements Runnable {
private volatile boolean goon = false;
private final Ticker ps;
private int aggressiveGCModificator;
private RunningAverage avgFreeMemory;
public MemoryChecker(Ticker ps, int modificator){
this.ps = ps;
this.aggressiveGCModificator = modificator;
}
protected synchronized void terminate() {
goon = false;
Logger.normal(this, "Terminating Memory Checker!");
}
public boolean isRunning() {
return goon;
}
public synchronized void start() {
goon = true;
Logger.normal(this, "Starting Memory Checker!");
run();
}
@Override
public void run() {
freenet.support.Logger.OSThread.logPID(this);
if(!goon){
Logger.normal(this, "Goon is false ; killing MemoryChecker");
return;
}
Runtime r = Runtime.getRuntime();
long totalMemory = r.totalMemory();
long freeMemory = r.freeMemory();
long maxMemory = r.maxMemory();
Logger.normal(this, "Memory in use: "+SizeUtil.formatSize((totalMemory-freeMemory)));
if (totalMemory == maxMemory || maxMemory == Long.MAX_VALUE) {
// jvm have allocated maximum memory
// totalMemory never decrease, so check it only for once
if (avgFreeMemory == null)
avgFreeMemory = new SimpleRunningAverage(3, freeMemory);
else
avgFreeMemory.report(freeMemory);
if (avgFreeMemory.countReports() >= 3 && avgFreeMemory.currentValue() < 4 * 1024 * 1024) {// average free memory < 4 MB
Logger.normal(this, "Reached threshold, checking for low memory ...");
System.gc();
System.runFinalization();
try {
Thread.sleep(10); // Force a context switch, finalization need a CS to complete
} catch (InterruptedException e) {
}
freeMemory = r.freeMemory();
avgFreeMemory.report(freeMemory);
}
}
int sleeptime = aggressiveGCModificator;
if(sleeptime <= 0) { // We are done
ps.queueTimedJob(this, 120 * 250); // 30 sec
return;
} else
ps.queueTimedJob(this, 120L * sleeptime);
// FIXME
// Do not remove until all known memory issues fixed,
// Especially #66
// This probably reduces performance, but it makes
// memory usage *more predictable*. This will make
// tracking down the sort of nasty unpredictable OOMs
// we are getting much easier.
if(aggressiveGCModificator > 0) {
boolean logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
long beforeGCUsedMemory = (r.totalMemory() - r.freeMemory());
if(logMINOR) Logger.minor(this, "Memory in use before GC: "+beforeGCUsedMemory);
long beforeGCTime = System.currentTimeMillis();
System.gc();
System.runFinalization();
long afterGCTime = System.currentTimeMillis();
long afterGCUsedMemory = (r.totalMemory() - r.freeMemory());
if(logMINOR) {
Logger.minor(this, "Memory in use after GC: "+afterGCUsedMemory);
Logger.minor(this, "GC completed after "+(afterGCTime - beforeGCTime)+"ms and \"recovered\" "+(beforeGCUsedMemory - afterGCUsedMemory)+" bytes, leaving "+afterGCUsedMemory+" bytes used");
}
}
}
}