yacy_search_server/source/net/yacy/kelondro/util/StandardMemoryStrategy.java
reger df83fcc4fc disable optimistic GC assumption in StandardMemoryStrategy
After several tests found that eom is not prevented. Major reason in testing was assumption future GC will free avg of last 5 GC.
Disabeling this check improved eom exceptions.

Added simplest testcase used for verification
2015-02-11 01:42:01 +01:00

235 lines
8.5 KiB
Java

// MemoryControl.java
// -------------------------------------------
// (C) 2005 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 22.09.2005 on http://yacy.net
//
// $LastChangedDate: 2011-08-18 00:24:17 +0200 (Do, 18. Aug 2011) $
// $LastChangedRevision: 7883 $
// $LastChangedBy: orbiter $
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// 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 for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package net.yacy.kelondro.util;
import java.util.concurrent.atomic.AtomicBoolean;
import net.yacy.cora.util.Memory;
/**
* Standard implementation to get information about memory usage or try to free some memory
*/
public class StandardMemoryStrategy extends MemoryStrategy {
private final long[] gcs = new long[5];
private int gcs_pos = 0;
private long properByte = 0L; // treshold
private long prevTreshold = 0L;
private int tresholdCount = 0;
private boolean proper = true;
public StandardMemoryStrategy() {
name = "Standard Memory Strategy";
error= false; //since this is the standard implementation we assume always false here
}
/**
* Runs the garbage collector if last garbage collection is more than last millis ago
* @param last time which must be passed since lased gc
* @param info additional info for log
*/
@Override
protected final synchronized boolean gc(final int last, final String info) { // thq
assert last >= 10000; // too many forced GCs will cause bad execution performance
final long elapsed = System.currentTimeMillis() - lastGC;
if (elapsed > last) {
final long before = free();
final long start = System.currentTimeMillis();
System.gc();
lastGC = System.currentTimeMillis();
final long after = free();
this.gcs[this.gcs_pos++] = after - before;
if (this.gcs_pos >= this.gcs.length) this.gcs_pos = 0;
if (log.isFine()) log.info("[gc] before: " + Formatter.bytesToString(before) +
", after: " + Formatter.bytesToString(after) +
", freed: " + Formatter.bytesToString(after - before) +
", rt: " + (lastGC - start) + " ms, call: " + info);
return true;
}
if (log.isFinest()) log.finest("[gc] no execute, last run: " + (elapsed / 1000) + " seconds ago, call: " + info);
return false;
}
/**
* This method calculates the average amount of bytes freed by the last GCs forced by this class
* @return the average amount of freed bytes of the last forced GCs or <code>0</code> if no
* GC has been run yet
*/
protected final long getAverageGCFree() {
long x = 0;
int y = 0;
for (final long gc : this.gcs)
if (gc != 0) {
x += gc;
y++;
}
return (y == 0) ? 0 : x / y;
}
/**
* memory that is free without increasing of total memory taken from os
* @return bytes
*/
@Override
protected final long free() {
return Memory.free();
}
/**
* memory that is available including increasing total memory up to maximum
* @return bytes
*/
@Override
protected final long available() {
return Memory.available();
}
/**
* maximum memory the Java virtual will allocate machine; may vary over time in some cases
* @return bytes
*/
@Override
protected final long maxMemory()
{
return Memory.maxMemory();
}
/**
* currently allocated memory in the Java virtual machine; may vary over time
* @return bytes
*/
@Override
protected final long total()
{
return Memory.total();
}
/**
* <p>Tries to free a specified amount of bytes.</p>
* <p>
* If the currently available memory is enough, the method returns <code>true</code> without
* performing additional steps. If not, the behaviour depends on the parameter <code>force</code>.
* If <code>false</code>, a Full GC is only performed if former GCs indicated that a GC should
* provide enough free memory. If former GCs didn't but <code>force</code> is set to <code>true</code>
* a Full GC is performed nevertheless.
* </p>
* <p>
* Setting the <code>force</code> parameter to false doesn't necessarily mean, that no GC may be
* performed by this method, if the currently available memory doesn't suffice!
* </p>
* <p><em>Be careful with this method as GCs should always be the last measure to take</em></p>
*
* @param size the requested amount of free memory in bytes
* @param force specifies whether a GC should be run even in case former GCs didn't provide enough memory
* @return whether enough memory could be freed (or is free) or not
*/
@Override
protected boolean request(final long size, final boolean force, AtomicBoolean shortStatus) {
if (size <= 0) return true;
final boolean r = request0(size, force);
shortStatus.set(!r);
return r;
}
private boolean request0(final long size, final boolean force) {
final long avg = getAverageGCFree();
// if (avg >= size) return true; // optimistic view, GC may just has happened
long avail = available();
if (avail >= size) return true;
if (log.isFine()) {
final String t = new Throwable("Stack trace").getStackTrace()[1].toString();
log.fine(t + " requested " + (size >> 10) + " KB, got " + (avail >> 10) + " KB");
}
if (force || avg == 0 || avg + avail >= size) {
// this is only called if we expect that an allocation of <size> bytes would cause the jvm to call the GC anyway
final long memBefore = avail;
final boolean performedGC = gc(10000, "serverMemory.runGC(...)");
avail = available();
if (performedGC) {
final long freed = avail - memBefore;
log.info("performed " + ((force) ? "explicit" : "necessary") + " GC, freed " + (freed >> 10)
+ " KB (requested/available/average: "
+ (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)");
}
checkProper(avail);
return this.proper && avail >= size;
}
if (log.isFine()) log.fine("former GCs indicate to not be able to free enough memory (requested/available/average: "
+ (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)");
return false;
}
/**
* memory that is currently bound in objects
* @return used bytes
*/
@Override
protected final long used() {
return Memory.used();
}
@Override
protected boolean properState() {
return this.proper;
}
@Override
protected void resetProperState() {
this.proper = true;
this.tresholdCount = 0;
}
/**
* set the memory to be available
*/
@Override
protected void setProperMbyte(final long mbyte) {
this.properByte = mbyte << 20; // convert to byte
this.tresholdCount = 0;
}
private void checkProper(final long available) {
// disable proper state if memory is less than treshold - 4 times, maximum 11 minutes between each detection
if ((available) < this.properByte) {
final long t = System.currentTimeMillis();
if(this.prevTreshold + 11L /* minutes */ * 60000L > t) {
this.tresholdCount++;
if(this.tresholdCount > 3 /* occurencies - 1 */) this.proper = false;
}
else this.tresholdCount = 1;
this.prevTreshold = t;
log.info("checkProper: below treshold; tresholdCount: " + this.tresholdCount + "; proper: " + this.proper);
}
else if (!this.proper && (available) > (this.properByte * 2L)) // we were wrong!
resetProperState();
}
}