yacy_search_server/source/de/anomic/kelondro/util/MemoryControl.java
orbiter ce1adf9955 serialized all logging using concurrency:
high-performance search query situations as seen in yacy-metager integration showed deadlock situation caused by synchronization effects inside of sun.java code. It appears that the logger is not completely safe against deadlock situations in concurrent calls of the logger. One possible solution would be a outside-synchronization with 'synchronized' statements, but that would further apply blocking on all high-efficient methods that call the logger. It is much better to do a non-blocking hand-over of logging lines and work off log entries with a concurrent log writer. This also disconnects IO operations from logging, which can also cause IO operation when a log is written to a file. This commit not only moves the logger from kelondro to yacy.logging, it also inserts the concurrency methods to realize non-blocking logging.

git-svn-id: https://svn.berlios.de/svnroot/repos/yacy/trunk@6078 6c8d7289-2bf4-0310-a012-ef5d649a1542
2009-06-15 21:19:54 +00:00

197 lines
8.0 KiB
Java

// 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: 2008-12-04 13:54:16 +0100 (Do, 04 Dez 2008) $
// $LastChangedRevision: 5379 $
// $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 de.anomic.kelondro.util;
import de.anomic.tools.Formatter;
import de.anomic.yacy.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();
public static long maxMemory = runtime.maxMemory(); // this value does never change during runtime
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;
/**
* 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
*/
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();
//System.out.println("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv");
final long start = System.currentTimeMillis();
System.gc();
//System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if you see this many times please report to forum");
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 <code>0</code> if no
* GC has been run yet
*/
public static long getAverageGCFree() {
long x = 0;
int y = 0;
for (int i=0; i<gcs.length; i++)
if (gcs[i] != 0) {
x += gcs[i];
y++;
}
return (y == 0) ? 0 : x / y;
}
/**
* memory that is free without increasing of total memory taken from os
* @return bytes
*/
public static final long free() {
return runtime.freeMemory();
}
/**
* memory that is available including increasing total memory up to maximum
* @return bytes
*/
public static final long available() {
return maxMemory - total() + free();
}
/**
* currently allocated memory in the Java virtual machine; may vary over time
* @return bytes
*/
public static final long total()
{
return runtime.totalMemory();
}
/**
* <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
*/
public static boolean request(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 <size> bytes would cause the jvm to call the GC anyway
final long memBefore = avail;
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)");
}
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
*/
public static long used() {
return total() - free();
}
/**
* main
* @param args
*/
public static void main(final String[] args) {
// try this with a jvm 1.4.2 and with a jvm 1.5 and compare results
final int mb = 1024 * 1024;
System.out.println("vm: " + System.getProperty("java.vm.version"));
System.out.println("computed max = " + (maxMemory / mb) + " mb");
final int alloc = 10000;
final 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 = " + (total() / mb) +
", free = " + (free() / mb) +
", max = " + (maxMemory / mb) +
", avail = " + (available() / mb));
}
}
}