diff --git a/defaults/yacy.init b/defaults/yacy.init index ac21351ca..142ee0aa0 100644 --- a/defaults/yacy.init +++ b/defaults/yacy.init @@ -953,6 +953,9 @@ disk.free.hardlimit = 1000 memory.acceptDHTabove = 50 memory.disabledDHT = false +# wether using standard memory strategy - or try generation memory strategy +memory.standardStrategy = true + # setting if execution of CGI files is allowed or not cgi.allow = false cgi.suffixes = cgi,pl diff --git a/htroot/PerformanceMemory_p.html b/htroot/PerformanceMemory_p.html index f56c965a7..0cb1161a8 100644 --- a/htroot/PerformanceMemory_p.html +++ b/htroot/PerformanceMemory_p.html @@ -22,6 +22,7 @@

simulate short memory status

+

use Standard Memory Strategy (current: #[memoryStrategy]#)

Memory Usage:

diff --git a/htroot/PerformanceMemory_p.java b/htroot/PerformanceMemory_p.java index 2375cdbf0..bc7ced24d 100644 --- a/htroot/PerformanceMemory_p.java +++ b/htroot/PerformanceMemory_p.java @@ -63,15 +63,18 @@ public class PerformanceMemory_p { prop.put("gc", "1"); } MemoryControl.setSimulatedShortStatus(post.containsKey("simulatedshortmemory")); + final boolean std = post.containsKey("useStandardmemoryStrategy"); + env.setConfig("memory.standardStrategy", std); + MemoryControl.setStandardStrategy(std); } prop.put("simulatedshortmemory.checked", MemoryControl.getSimulatedShortStatus() ? 1 : 0); + prop.put("useStandardmemoryStrategy.checked", env.getConfigBool("memory.standardStrategy", true) ? 1 : 0); + prop.put("memoryStrategy", MemoryControl.getStrategyName()); - final long memoryFreeNow = MemoryControl.free(); final long memoryFreeAfterInitBGC = env.getConfigLong("memoryFreeAfterInitBGC", 0L); final long memoryFreeAfterInitAGC = env.getConfigLong("memoryFreeAfterInitAGC", 0L); final long memoryFreeAfterStartup = env.getConfigLong("memoryFreeAfterStartup", 0L); - final long memoryTotalNow = MemoryControl.total(); final long memoryTotalAfterInitBGC = env.getConfigLong("memoryTotalAfterInitBGC", 0L); final long memoryTotalAfterInitAGC = env.getConfigLong("memoryTotalAfterInitAGC", 0L); final long memoryTotalAfterStartup = env.getConfigLong("memoryTotalAfterStartup", 0L); @@ -80,19 +83,19 @@ public class PerformanceMemory_p { prop.putNum("memoryAvailAfterStartup", (MemoryControl.maxMemory() - memoryTotalAfterStartup + memoryFreeAfterStartup) / MB); prop.putNum("memoryAvailAfterInitBGC", (MemoryControl.maxMemory() - memoryTotalAfterInitBGC + memoryFreeAfterInitBGC) / MB); prop.putNum("memoryAvailAfterInitAGC", (MemoryControl.maxMemory() - memoryTotalAfterInitAGC + memoryFreeAfterInitAGC) / MB); - prop.putNum("memoryAvailNow", (MemoryControl.maxMemory() - memoryTotalNow + memoryFreeNow) / MB); + prop.putNum("memoryAvailNow", MemoryControl.available() / MB); prop.putNum("memoryTotalAfterStartup", memoryTotalAfterStartup / KB); prop.putNum("memoryTotalAfterInitBGC", memoryTotalAfterInitBGC / KB); prop.putNum("memoryTotalAfterInitAGC", memoryTotalAfterInitAGC / KB); - prop.putNum("memoryTotalNow", memoryTotalNow / MB); + prop.putNum("memoryTotalNow", MemoryControl.total() / MB); prop.putNum("memoryFreeAfterStartup", memoryFreeAfterStartup / KB); prop.putNum("memoryFreeAfterInitBGC", memoryFreeAfterInitBGC / KB); prop.putNum("memoryFreeAfterInitAGC", memoryFreeAfterInitAGC / KB); - prop.putNum("memoryFreeNow", memoryFreeNow / MB); + prop.putNum("memoryFreeNow", MemoryControl.free() / MB); prop.putNum("memoryUsedAfterStartup", (memoryTotalAfterStartup - memoryFreeAfterStartup) / KB); prop.putNum("memoryUsedAfterInitBGC", (memoryTotalAfterInitBGC - memoryFreeAfterInitBGC) / KB); prop.putNum("memoryUsedAfterInitAGC", (memoryTotalAfterInitAGC - memoryFreeAfterInitAGC) / KB); - prop.putNum("memoryUsedNow", (memoryTotalNow - memoryFreeNow) / MB); + prop.putNum("memoryUsedNow", MemoryControl.used() / MB); // write table for Table index sizes Iterator i = Table.filenames(); diff --git a/htroot/PerformanceQueues_p.java b/htroot/PerformanceQueues_p.java index c045698f4..7aaf98ebe 100644 --- a/htroot/PerformanceQueues_p.java +++ b/htroot/PerformanceQueues_p.java @@ -101,7 +101,7 @@ public class PerformanceQueues_p { sb.setConfig(SwitchboardConstants.MEMORY_ACCEPTDHT, post.getInt("memoryAcceptDHT", 50)); } if(post.containsKey("resetObserver")) { - MemoryControl.setDHTallowed(); + MemoryControl.resetProperState(); } } final Map defaultSettings = ((post == null) || (!(post.containsKey("submitdefault")))) ? null : FileUtils.loadMap(defaultSettingsFile); @@ -350,7 +350,7 @@ public class PerformanceQueues_p { final long diskFree = sb.getConfigLong(SwitchboardConstants.DISK_FREE, 3000L); final long diskFreeHardlimit = sb.getConfigLong(SwitchboardConstants.DISK_FREE_HARDLIMIT, 1000L); final long memoryAcceptDHT = sb.getConfigLong(SwitchboardConstants.MEMORY_ACCEPTDHT, 50000L); - final boolean observerTrigger = !MemoryControl.getDHTallowed(); + final boolean observerTrigger = !MemoryControl.properState(); prop.put("diskFree", diskFree); prop.put("diskFreeHardlimit", diskFreeHardlimit); prop.put("memoryAcceptDHT", memoryAcceptDHT); diff --git a/source/de/anomic/crawler/ResourceObserver.java b/source/de/anomic/crawler/ResourceObserver.java index c9b6cf641..3e30e1dc6 100644 --- a/source/de/anomic/crawler/ResourceObserver.java +++ b/source/de/anomic/crawler/ResourceObserver.java @@ -62,7 +62,7 @@ public class ResourceObserver { * checks the resources and pauses crawls if necessary */ public void resourceObserverJob() { - MemoryControl.setDHTMbyte(getMinFreeMemory()); + MemoryControl.setProperMbyte(getMinFreeMemory()); normalizedDiskFree = getNormalizedDiskFree(); normalizedMemoryFree = getNormalizedMemoryFree(); @@ -124,7 +124,7 @@ public class ResourceObserver { } private Space getNormalizedMemoryFree() { - if(!MemoryControl.getDHTallowed()) return Space.LOW; + if(!MemoryControl.properState()) return Space.LOW; return Space.HIGH; } diff --git a/source/net/yacy/kelondro/util/GenerationMemoryStrategy.java b/source/net/yacy/kelondro/util/GenerationMemoryStrategy.java new file mode 100644 index 000000000..a2324086a --- /dev/null +++ b/source/net/yacy/kelondro/util/GenerationMemoryStrategy.java @@ -0,0 +1,206 @@ +// MemoryControl.java +// ------------------------------------------- +// (C) 2011 by Sebastian Gaebel +// first published 22.08.2011 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.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; + +public class GenerationMemoryStrategy extends MemoryStrategy { + + private MemoryPoolMXBean eden, survivor, old; + private MemoryMXBean heap; + + public GenerationMemoryStrategy() { + name = "Generation Memory Strategy"; + error = initPoolBeans(); + heap = ManagementFactory.getMemoryMXBean(); + if (lastGC == 0l) gc(10000, "initial gc - to get proper results"); // this is necessary on some GCs / vm + if (error) log.logWarning(name + ": not a generational heap"); + } + + /** + * memory that is free without increasing of total memory taken from os + * @return bytes + */ + protected final long free() { + return youngUsage(false).getCommitted() - youngUsage(false).getUsed(); + } + + /** + * memory that is available including increasing total memory up to maximum + * Smallest of both old and young + * @return bytes + */ + protected final long available() { + return available(false); + } + + /** + * memory that is available including increasing total memory up to maximum + * @param force specifies whether ignoring prefered size + * @return bytes + */ + private final long available(final boolean force) { + return force & properState() ? Math.max(youngAvailable(), oldAvailable()) : Math.min(youngAvailable(), oldAvailable()); + } + + /** + * memory that is currently bound in objects + * @return used bytes + */ + protected final long used() { + return heap.getHeapMemoryUsage().getUsed(); + } + + /** + * currently allocated memory in the Java virtual machine; may vary over time + * @return bytes + */ + protected final long total() { + return heap.getHeapMemoryUsage().getCommitted(); + } + + /** + * maximum memory the Java virtual will allocate machine; may vary over time in some cases + * @return bytes + */ + protected final long maxMemory() { + return heap.getHeapMemoryUsage().getMax(); + } + + /** + * checks if a specified amount of bytes are available + * after the jvm recycled unused objects + * + * @param size the requested amount of free memory in bytes + * @param force specifies whether ignoring prefered size + * @return whether enough memory could be freed (or is free) or not + */ + protected final boolean request(final long size, final boolean force, boolean shortStatus) { + if (size == 0l) return true; // does not make sense to check - returning true without setting shortStatus (which also doesn't make sense to me) + final boolean unknown = size < 0l; // size < 0 indicate an unknown size - maybe from gziped streams + final boolean r = unknown? properState() : size < available(force); + shortStatus = !r; + return r; + } + + /** + * use this to check for temporary space + * @return bytes available to allocate in Eden Space (Young Generation) + */ + protected final long youngAvailable() { + return youngUsage(true).getCommitted() - youngUsage(true).getUsed(); + } + + /** + * @return bytes available to allocate in Tenured Space (Old Generation) + */ + protected final long oldAvailable() { + return oldUsage(true).getCommitted() - oldUsage(true).getUsed(); + } + + /** + * alive objects get 'moved' on gc from eden space to survior and from survior to old gen + * in a worse case (all objects of survivor alive) all objects get 'moved' from suvivor to old gen + * this method checks if there is is space left in old gen for that + * + * @return Memory is in proper state + */ + protected boolean properState() { + return (oldUsage(true).getUsed() + survivorUsage(false).getCommitted()) < oldUsage(false).getCommitted(); + } + + /** + * based on my research for a proper running jvm, this gives a guidance value for the heap size + * + * @return bytes recommend for heap size + */ + protected long recommendHeapSize() { + // the heap/old-ration is jvm-specific and can be changed by parameter - using this + 20% buffer + final double factor = 1.2 * (double)heap.getHeapMemoryUsage().getMax() / (double)oldUsage(false).getMax(); + // current needed space in old gen + final long neededOld = oldUsage(true).getUsed() + survivorUsage(false).getMax(); + return (long) (neededOld * factor); + } + + /** + * @param collected specifies whether trying to get the memory usage after the jvm recycled unused objects + * @return MemoryUsage of Eden Space aka Young Gen + */ + private MemoryUsage youngUsage(final boolean collected) { + if (collected) { + final MemoryUsage usage = eden.getCollectionUsage(); + if (usage != null) return usage; + error = true; + log.logWarning(name + ": no young colletion usage available"); + } + return eden.getUsage(); + } + + /** + * @param collected specifies whether trying to get the memory usage after the jvm recycled unused objects + * @return MemoryUsage of Survivor + */ + private MemoryUsage survivorUsage(final boolean collected) { + if (collected) { + final MemoryUsage usage = survivor.getCollectionUsage(); + if (usage != null) return usage; + error = true; + log.logWarning(name + ": no survivior colletion usage available"); + } + return survivor.getUsage(); + } + + /** + * @param collected specifies whether trying to get the memory usage after the jvm recycled unused objects + * @return MemoryUsage of Old Gen + */ + private MemoryUsage oldUsage(final boolean collected) { + if (collected) { + final MemoryUsage usage = old.getCollectionUsage(); + if (usage != null) return usage; + error = true; + log.logWarning(name + ": no old colletion usage available"); + } + return old.getUsage(); + } + + private boolean initPoolBeans() { + for (final MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) { + if (bean.getName().startsWith("G1")) break; //this strategy will not run on G1 + if (bean.getName().contains("Eden")) { + eden = bean; + } else if (bean.getName().contains("Survivor")) { + survivor = bean; + } else if (bean.getName().contains("Old") || bean.getName().contains("Tenured")) { + old = bean; + } + } + return eden == null || survivor == null || old == null; + } +} diff --git a/source/net/yacy/kelondro/util/MemoryControl.java b/source/net/yacy/kelondro/util/MemoryControl.java index 2657bd375..55f2e4a8c 100644 --- a/source/net/yacy/kelondro/util/MemoryControl.java +++ b/source/net/yacy/kelondro/util/MemoryControl.java @@ -25,25 +25,43 @@ package net.yacy.kelondro.util; -import net.yacy.kelondro.logging.Log; - /** * Use this to get information about memory usage or try to free some memory */ public class MemoryControl { - - private static final Runtime runtime = Runtime.getRuntime(); - private static final Log log = new Log("MEMORY"); - - private static final long[] gcs = new long[5]; - private static int gcs_pos = 0; - private static long lastGC = 0l; - private static long DHTMbyte = 0L; - private static long prevDHTtreshold = 0L; - private static int DHTtresholdCount = 0; - private static boolean allowDHT = true; - private static boolean shortStatus = false, simulatedShortStatus = false; + private static boolean shortStatus = false, simulatedShortStatus = false, usingStandardStrategy = true; + private static MemoryStrategy strategy; + + private static MemoryStrategy getStrategy() { + if (strategy == null || strategy.hasError()) { + if (!usingStandardStrategy) { + strategy = new GenerationMemoryStrategy(); + // fall back if error detected + if (strategy.hasError()) { + usingStandardStrategy = true; + strategy = new StandardMemoryStrategy(); + } + } else { + strategy = new StandardMemoryStrategy(); + } + } + return strategy; + } + + public final static void setStandardStrategy(final boolean std) { + if (usingStandardStrategy != std) { + usingStandardStrategy = std; + strategy = null; + } + } + + /** + * @return the name of the used strategy + */ + public final static String getStrategyName() { + return getStrategy().getName(); + } /** * Runs the garbage collector if last garbage collection is more than last millis ago @@ -51,42 +69,7 @@ public class MemoryControl { * @param info additional info for log */ public final synchronized static 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(); - gcs[gcs_pos++] = after - before; - if (gcs_pos >= gcs.length) gcs_pos = 0; - - if (log.isFine()) log.logInfo("[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.logFinest("[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 0 if no - * GC has been run yet - */ - public static long getAverageGCFree() { - long x = 0; - int y = 0; - for (final long gc : gcs) - if (gc != 0) { - x += gc; - y++; - } - return (y == 0) ? 0 : x / y; + return getStrategy().gc(last, info); } /** @@ -94,7 +77,7 @@ public class MemoryControl { * @return bytes */ public static final long free() { - return runtime.freeMemory(); + return getStrategy().free(); } /** @@ -102,7 +85,7 @@ public class MemoryControl { * @return bytes */ public static final long available() { - return maxMemory() - total() + free(); + return getStrategy().available(); } /** @@ -111,7 +94,7 @@ public class MemoryControl { */ public static final long maxMemory() { - return runtime.maxMemory(); + return getStrategy().maxMemory(); } /** @@ -120,62 +103,18 @@ public class MemoryControl { */ public static final long total() { - return runtime.totalMemory(); + return getStrategy().total(); } /** - *

Tries to free a specified amount of bytes.

- *

- * If the currently available memory is enough, the method returns true without - * performing additional steps. If not, the behaviour depends on the parameter force. - * If false, a Full GC is only performed if former GCs indicated that a GC should - * provide enough free memory. If former GCs didn't but force is set to true - * a Full GC is performed nevertheless. - *

- *

- * Setting the force parameter to false doesn't necessarily mean, that no GC may be - * performed by this method, if the currently available memory doesn't suffice! - *

- *

Be careful with this method as GCs should always be the last measure to take

- * + * check for a specified amount of bytes + * * @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 + * @param force specifies whether risk an expensive GC * @return whether enough memory could be freed (or is free) or not */ public static boolean request(final long size, final boolean force) { - if (size <= 0) return true; - final boolean r = request0(size, force); - shortStatus = !r; - return r; - } - private static boolean request0(final long size, final boolean force) { - final long avg = getAverageGCFree(); - if (avg >= size) return true; - long avail = available(); - if (avail >= size) return true; - if (log.isFine()) { - final String t = new Throwable("Stack trace").getStackTrace()[1].toString(); - log.logFine(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 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.logInfo("performed " + ((force) ? "explicit" : "necessary") + " GC, freed " + (freed >> 10) - + " KB (requested/available/average: " - + (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)"); - } - checkDHTrule(avail); - return avail >= size; - } else { - if (log.isFine()) log.logFine("former GCs indicate to not be able to free enough memory (requested/available/average: " - + (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)"); - return false; - } + return getStrategy().request(size, force, shortStatus); } /** @@ -194,6 +133,9 @@ public class MemoryControl { return simulatedShortStatus; } + /** + * @return if last request failed + */ public static boolean shortStatus() { //if (shortStatus) System.out.println("**** SHORT MEMORY ****"); return simulatedShortStatus || shortStatus; @@ -204,56 +146,50 @@ public class MemoryControl { * @return used bytes */ public static long used() { - return total() - free(); - } - - public static boolean getDHTallowed() { - return allowDHT; + return getStrategy().used(); } - public static void setDHTallowed() { - allowDHT = true; - DHTtresholdCount = 0; + /** + * @return if Memory seams to be in a proper state + */ + public static boolean properState() { + return getStrategy().properState(); } /** - * set the memory to be available + * forced enable properState - StandardMemoryStrategy only */ - public static void setDHTMbyte(final long mbyte) { - DHTMbyte = mbyte; - DHTtresholdCount = 0; + public static void resetProperState() { + getStrategy().resetProperState(); } - private static void checkDHTrule(final long available) { - // disable dht if memory is less than treshold - 4 times, maximum 11 minutes between each detection - if ((available >> 20) < DHTMbyte) { - final long t = System.currentTimeMillis(); - if(prevDHTtreshold + 11L /* minutes */ * 60000L > t) { - DHTtresholdCount++; - if(DHTtresholdCount > 3 /* occurencies - 1 */) allowDHT = false; - } - else DHTtresholdCount = 1; - - prevDHTtreshold = t; - - log.logInfo("checkDHTrule: below treshold; tresholdCount: " + DHTtresholdCount + "; allowDHT: " + allowDHT); - } - else if (!allowDHT && (available >> 20) > (DHTMbyte * 2L)) // we were wrong! - setDHTallowed(); + /** + * set the memory to be available for properState - StandardMemoryStrategy only + */ + public static void setProperMbyte(final long mbyte) { + getStrategy().setProperMbyte(mbyte); } /** * main - * @param args + * @param args use 'force' to request by force, use 'std' / 'gen' to specify strategy */ public static void main(final String[] args) { - // try this with a jvm 1.4.2 and with a jvm 1.5 and compare results + // try this with different strategy and compare results final int mb = 1024 * 1024; + boolean force = false; + for (final String arg : args) { + if (arg.equals("force")) force = true; + if (arg.equalsIgnoreCase("gen")) usingStandardStrategy = false; + if (arg.equalsIgnoreCase("std")) usingStandardStrategy = true; + } System.out.println("vm: " + System.getProperty("java.vm.version")); System.out.println("computed max = " + (maxMemory() / mb) + " mb"); + System.out.println("using " + getStrategyName()); final byte[][] x = new byte[100000][]; + for (int i = 0; i < 100000; i++) { - if (request(mb, false)) + if (request(mb, force)) { x[i] = new byte[mb]; System.out.println("used = " + i + " / " + (used() /mb) + @@ -261,8 +197,8 @@ public class MemoryControl { ", free = " + (free() / mb) + ", max = " + (maxMemory() / mb) + ", avail = " + (available() / mb) + - ", averageGC = " + getAverageGCFree()); - } + (usingStandardStrategy? ", averageGC = " + ((StandardMemoryStrategy)getStrategy()).getAverageGCFree() : "")); + } else System.exit(0); } } diff --git a/source/net/yacy/kelondro/util/MemoryStrategy.java b/source/net/yacy/kelondro/util/MemoryStrategy.java new file mode 100644 index 000000000..3df8b2dfa --- /dev/null +++ b/source/net/yacy/kelondro/util/MemoryStrategy.java @@ -0,0 +1,138 @@ +// MemoryControl.java +// ------------------------------------------- +// (C) 2011 by Sebastian Gaebel +// first published 22.08.2011 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.lang.management.ManagementFactory; + +import net.yacy.kelondro.logging.Log; + +public abstract class MemoryStrategy { + + protected final static Log log = new Log("MEMORY"); + protected static long lastGC = 0l; + protected static boolean error = true; + protected static String name; + + /** + * @return if an error has been detected + */ + protected final boolean hasError() { + return error; + } + + /** + * @return an identifying name + */ + protected final String getName() { + return name; + } + + /** + * 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 + */ + protected 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) { + ManagementFactory.getMemoryMXBean().gc(); + lastGC = System.currentTimeMillis(); + return true; + } + + if (log.isFinest()) log.logFinest("[gc] no execute, last run: " + (elapsed / 1000) + " seconds ago, call: " + info); + return false; + } + + /** + * memory that is free without increasing of total memory taken from os + * @return bytes + */ + protected abstract long free(); + + /** + * memory that is available including increasing total memory up to maximum + * @return bytes + */ + protected abstract long available(); + + /** + * memory that is currently bound in objects + * @return used bytes + */ + protected abstract long used(); + + /** + * currently allocated memory in the Java virtual machine; may vary over time + * @return bytes + */ + protected abstract long total(); + + /** + * maximum memory the Java virtual will allocate machine; may vary over time in some cases + * @return bytes + */ + protected abstract long maxMemory(); + + /** + *

Tries to free a specified amount of bytes.

+ *

+ * If the currently available memory is enough, the method returns true without + * performing additional steps. If not, the behaviour depends on the parameter force. + * If false, a Full GC is only performed if former GCs indicated that a GC should + * provide enough free memory. If former GCs didn't but force is set to true + * a Full GC is performed nevertheless. + *

+ *

+ * Setting the force parameter to false doesn't necessarily mean, that no GC may be + * performed by this method, if the currently available memory doesn't suffice! + *

+ *

Be careful with this method as GCs should always be the last measure to take

+ * + * @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 + */ + protected abstract boolean request(final long size, final boolean force, boolean shortStatus); + + /** + * @return if Memory seams to be in a proper state + */ + protected abstract boolean properState(); + + /** + * forced enable properState - StandardMemoryStrategy only + */ + protected void resetProperState() { + } + + /** + * set the memory to be available for properState - StandardMemoryStrategy only + */ + protected void setProperMbyte(final long mbyte) { + } +} diff --git a/source/net/yacy/kelondro/util/StandardMemoryStrategy.java b/source/net/yacy/kelondro/util/StandardMemoryStrategy.java new file mode 100644 index 000000000..581a21d8d --- /dev/null +++ b/source/net/yacy/kelondro/util/StandardMemoryStrategy.java @@ -0,0 +1,223 @@ +// 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; + +/** + * Standard implementation to get information about memory usage or try to free some memory + */ +public class StandardMemoryStrategy extends MemoryStrategy { + + + private final Runtime runtime = Runtime.getRuntime(); + private final long[] gcs = new long[5]; + private int gcs_pos = 0; + private long properMbyte = 0L; + 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 + */ + 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(); + gcs[gcs_pos++] = after - before; + if (gcs_pos >= gcs.length) gcs_pos = 0; + + if (log.isFine()) log.logInfo("[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.logFinest("[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 0 if no + * GC has been run yet + */ + protected final long getAverageGCFree() { + long x = 0; + int y = 0; + for (final long gc : 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 + */ + protected final long free() { + return runtime.freeMemory(); + } + + /** + * memory that is available including increasing total memory up to maximum + * @return bytes + */ + protected final long available() { + return maxMemory() - total() + free(); + } + + /** + * maximum memory the Java virtual will allocate machine; may vary over time in some cases + * @return bytes + */ + protected final long maxMemory() + { + return runtime.maxMemory(); + } + + /** + * currently allocated memory in the Java virtual machine; may vary over time + * @return bytes + */ + protected final long total() + { + return runtime.totalMemory(); + } + + /** + *

Tries to free a specified amount of bytes.

+ *

+ * If the currently available memory is enough, the method returns true without + * performing additional steps. If not, the behaviour depends on the parameter force. + * If false, a Full GC is only performed if former GCs indicated that a GC should + * provide enough free memory. If former GCs didn't but force is set to true + * a Full GC is performed nevertheless. + *

+ *

+ * Setting the force parameter to false doesn't necessarily mean, that no GC may be + * performed by this method, if the currently available memory doesn't suffice! + *

+ *

Be careful with this method as GCs should always be the last measure to take

+ * + * @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 + */ + protected boolean request(final long size, final boolean force, boolean shortStatus) { + if (size <= 0) return true; + final boolean r = request0(size, force); + shortStatus = !r; + return r; + } + private boolean request0(final long size, final boolean force) { + final long avg = getAverageGCFree(); + if (avg >= size) return true; + long avail = available(); + if (avail >= size) return true; + if (log.isFine()) { + final String t = new Throwable("Stack trace").getStackTrace()[1].toString(); + log.logFine(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 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.logInfo("performed " + ((force) ? "explicit" : "necessary") + " GC, freed " + (freed >> 10) + + " KB (requested/available/average: " + + (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)"); + } + checkProper(avail); + return avail >= size; + } else { + if (log.isFine()) log.logFine("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 + */ + protected final long used() { + return total() - free(); + } + + protected boolean properState() { + return proper; + } + + protected void resetProperState() { + proper = true; + tresholdCount = 0; + } + + /** + * set the memory to be available + */ + protected void setProperMbyte(final long mbyte) { + properMbyte = mbyte; + 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 >> 20) < properMbyte) { + final long t = System.currentTimeMillis(); + if(prevTreshold + 11L /* minutes */ * 60000L > t) { + tresholdCount++; + if(tresholdCount > 3 /* occurencies - 1 */) proper = false; + } + else tresholdCount = 1; + + prevTreshold = t; + + log.logInfo("checkProper: below treshold; tresholdCount: " + tresholdCount + "; proper: " + proper); + } + else if (!proper && (available >> 20) > (properMbyte * 2L)) // we were wrong! + resetProperState(); + } + +} diff --git a/source/net/yacy/yacy.java b/source/net/yacy/yacy.java index 1b3e3431f..082b6c398 100644 --- a/source/net/yacy/yacy.java +++ b/source/net/yacy/yacy.java @@ -209,6 +209,9 @@ public final class yacy { } sb = new Switchboard(dataHome, appHome, "defaults/yacy.init".replace("/", File.separator), newconf); //sbSync.V(); // signal that the sb reference was set + + // switch the memory strategy + MemoryControl.setStandardStrategy(sb.getConfigBool("memory.standardStrategy", true)); // save information about available memory at startup time sb.setConfig("memoryFreeAfterStartup", startupMemFree);