// serverMemory.java // ------------------------------------------- // (C) 2005 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany // first published 22.09.2005 on http://yacy.net // // $LastChangedDate$ // $LastChangedRevision$ // $LastChangedBy$ // // 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 de.anomic.server; import de.anomic.server.logging.serverLog; import de.anomic.tools.yFormatter; public class serverMemory { public static boolean vm14 = System.getProperty("java.vm.version").startsWith("1.4"); public static final long max = (vm14) ? computedMaxMemory() : Runtime.getRuntime().maxMemory() ; // patch for maxMemory bug in Java 1.4.2 private static final Runtime runtime = Runtime.getRuntime(); private static final serverLog log = new serverLog("MEMORY"); private static final long[] gcs = new long[5]; private static int gcs_pos = 0; private static long lastGC; public final synchronized static void gc(int last, String info) { // thq long elapsed = System.currentTimeMillis() - lastGC; if (elapsed > last) { long free = free(); System.gc(); lastGC = System.currentTimeMillis(); log.logInfo("[gc] before: " + bytesToString(free) + ", after: " + bytesToString(free()) + ", call: " + info); } else if (log.isFine()) { log.logFine("[gc] no execute, last run: " + (elapsed / 1000) + " seconds ago, call: " + info); } } /** @return the amount of freed bytes by a forced GC this method performes */ private static long runGC(final boolean count) { final long memnow = available(); gc(1000, "serverMemory.runGC(...)"); final long freed = available() - memnow; if (count) { gcs[gcs_pos] = freed; gcs_pos = (gcs_pos + 1) % gcs.length; } return freed; } /** * 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 (int i=0; itrue without performing additional steps. Otherwise - if * gciffail is set - a Full GC is run, if gciffail is set to * false and not enough memory is available, this method returns false. * * @see serverMemory#request(long, boolean) for another implementation * @param memory amount of bytes to be assured to be free * @param gciffail if not enough memory is available, this parameter specifies whether to perform * a Full GC to free enough RAM * @return whether enough RAM is available * @deprecated use {@link serverMemory#request(long, boolean)} instead to enable the collection of * heuristics about explicit usage of Full GCs. */ public static boolean available(long memory, boolean gciffail) { if (available() >= memory) return true; if (!gciffail) return false; gc(4000, "serverMemory.available(...)"); return (available() >= memory); } public static long available() { // memory that is available including increasing total memory up to maximum return max - runtime.totalMemory() + runtime.freeMemory(); } /** *

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 */ public static boolean request(final long size, final boolean force) { long avail = available(); if (avail >= size) return true; if (log.isFine()) { String t = new Throwable("Stack trace").getStackTrace()[1].toString(); log.logFine(t + " requested " + (size >> 10) + " KB, got " + (avail >> 10) + " KB"); } final long avg = getAverageGCFree(); 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 freed = runGC(!force); avail = available(); log.logInfo("performed " + ((force) ? "explicit" : "necessary") + " GC, freed " + (freed >> 10) + " KB (requested/available/average: " + (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)"); return avail >= size; } else { log.logInfo("former GCs indicate to not be able to free enough memory (requested/available/average: " + (size >> 10) + " / " + (avail >> 10) + " / " + (avg >> 10) + " KB)"); return false; } } public static long used() { // memory that is currently bound in objects return runtime.totalMemory() - runtime.freeMemory(); } public static String bytesToString(long byteCount) { try { final StringBuffer byteString = new StringBuffer(); if (byteCount > 1073741824) { byteString.append(yFormatter.number((double)byteCount / (double)1073741824 )) .append(" GB"); } else if (byteCount > 1048576) { byteString.append(yFormatter.number((double)byteCount / (double)1048576)) .append(" MB"); } else if (byteCount > 1024) { byteString.append(yFormatter.number((double)byteCount / (double)1024)) .append(" KB"); } else { byteString.append(Long.toString(byteCount)) .append(" Bytes"); } return byteString.toString(); } catch (Exception e) { return "unknown"; } } private static int computedMaxMemory() { // there is a bug in java 1.4.2 for maxMemory() // see for bug description: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4686462 // to get the correct maxMemory, we force a OutOfMemoryError here to measure the 'real' maxMemory() int mb = 1024 * 1024; byte[][] x = new byte[2048][]; for (int i = 0; i < x.length; i++) { try { x[i] = new byte[mb]; } catch (OutOfMemoryError e) { x = null; // free memory //System.out.println("* computed maxMemory = " + i + " mb"); return (int) Math.max(i * mb, Runtime.getRuntime().totalMemory()); } } return 2048 * mb; } public static void main(String[] args) { // try this with a jvm 1.4.2 and with a jvm 1.5 and compare results int mb = 1024 * 1024; System.out.println("vm: " + System.getProperty("java.vm.version")); System.out.println("computed max = " + (computedMaxMemory() / mb) + " mb"); int alloc = 10000; Runtime rt = Runtime.getRuntime(); byte[][] x = new byte[100000][]; for (int i = 0; i < 100000; i++) { x[i] = new byte[alloc]; if (i % 100 == 0) System.out.println("used = " + (i * alloc / mb) + ", total = " + (rt.totalMemory() / mb) + ", free = " + (rt.freeMemory() / mb) + ", max = " + (rt.maxMemory() / mb) + ", avail = " + ((rt.maxMemory() - rt.totalMemory() + rt.freeMemory()) / mb)); } } }