/************************************************************************** * Parts copyright (c) 2001, 2002, 2003 by Punch Telematix. * * All rights reserved. * * Parts copyright (c) 2004, 2005 by Chris Gray, /k/ Embedded Java * * Solutions. All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions* * nor the names of other contributors may be used to endorse or promote* * products derived from this software without specific prior written * * permission. * * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER * * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package wonka.vm; public final class GarbageCollector implements Runnable { /** ** The unique instance of this class (it's a singleton). */ private static GarbageCollector theGarbageCollector; /** ** The associated Finalizer singleton. */ private static Finalizer theFinalizer; /** ** Be verbose if this flag is true (set from wonka.properties). */ private static boolean verbose = false; /** ** The priority at which the garbage collection thread is started. */ private static final int INITIAL_PRIORITY = 5; /** ** The minimum priority at which the GC thread will run. */ private static final int MIN_PRIORITY = 5; // CG 20040428 : WAS : 1 /** ** The maximum priority at which the GC thread will run. */ private static final int MAX_PRIORITY = 10; /** ** HIGHER_PRIORITY_THRESHOLD: when the occupancy of the heap rises above this ** percentage, we start to increase the priority of the gc thread. */ private static final float HIGHER_PRIORITY_THRESHOLD = 0.60f; /** ** LOWER_PRIORITY_THRESHOLD: when the occupancy of the heap falls below this ** percentage, we start to gradually reduce the priority of the gc thread. */ private static final float LOWER_PRIORITY_THRESHOLD = 0.40f; /** ** Priority of the GC thread. Updated after each cycle, depending ** on the occupancy of the cache. Boosted to MAX_PRIORITY each time ** we are `kicked'. (N.B. a `kick' can also come from native code). */ private int priority = INITIAL_PRIORITY; /** ** Numver of GC cycles completed so far. */ private int passes; /** ** Number of times we have been `kicked'. */ private int kicks; /* ** The timestamp at the start of the most recent sleep. */ private long timestamp_previous; /** ** The amount of memory available at the start of the most recent sleep. */ private long available_previous; /** ** Time to sleep between GC cycles. Updated after each cycle, depending ** on whether our collection rate is rising or falling. */ private int sleep_millis = 10000; /** ** The minimum time for which GC will sleep between cycles. ** Can be adjusted using setMinSleep(). */ private int min_sleep_millis = 1000; /** ** The maximum time for which GC will sleep between cycles. ** Can be adjusted using setMaxSleep(). */ private int max_sleep_millis = 60000; /** ** The identity of the thread which performs GC. */ private Thread thread; /** ** Create the native code structures used by GC. */ private native void create(); /** ** Implement verbosity */ private static void debug(String s) { if (verbose) { System.err.println(s); } } /** ** Return the solitary instance of GarbageCollector. */ public static synchronized GarbageCollector getInstance() { if (theGarbageCollector == null) { String debugProperty = GetSystemProperty.MIKA_VERBOSE; if (debugProperty.indexOf("gc") >= 0) { verbose = true; } theGarbageCollector = new GarbageCollector(); synchronized (theGarbageCollector) { while (theFinalizer == null) { try { theGarbageCollector.wait(1000); } catch (InterruptedException ie) {} } } } return theGarbageCollector; } /** ** Initialise variables, launch finalisation thread. This method is called ** from run(), and getInstance() will not return until this method has ** completed (so we don't proceed with a half-initialied GC system). */ private void init() { create(); theFinalizer = Finalizer.getInstance(); Thread thread = new Thread(theFinalizer, "Confessor"); thread.setPriority(10); thread.setDaemon(true); thread.start(); } /** ** Our very private constructor. */ private GarbageCollector() { debug("GC: Starting Undertaker thread at priority " + priority + ", sleep time = " + sleep_millis + " milliseconds"); thread = new Thread(this, "Undertaker"); thread.setPriority(priority); thread.setDaemon(true); thread.start(); } /** ** Run the native code which performs a GC cycle. */ private native void collect(); /** ** Kick the GarbageCollector, make it do some work instead of sleeping. ** Will throw NullPointerException if GC thread is not yet started. */ public synchronized void kick() { debug("GC: kicked into action by thread " + Thread.currentThread()); if (kicks < 3) { kicks += 3; } // [CG 20050620] WAS: thread.interrupt(); this.notifyAll(); } /** ** Wait for the GarbageCollector to do a certain number of passes. ** Will throw NullPointerException if GC thread is not yet started. public synchronized void work(int numPasses) { debug("GC: thread " + Thread.currentThread() + " is waiting until " + numPasses + " have been performed"); int wait_until = passes + numPasses; while (passes - wait_until > 0) { try { wait(); } catch (InterruptedException ie) { debug("GC: thread " + Thread.currentThread() + " was interrupted while waiting"); } } debug("GC: thread " + Thread.currentThread() + " resuming after " + (passes -wait_until) + " were performed"); } */ /** ** Ask GC to free up a certain number of bytes of memory. ** Returns the number of bytes which were really freed ** (can be more or less). */ public native synchronized int request(int bytes); public void setMinSleep(int millis) { min_sleep_millis = millis; } public void setMaxSleep(int millis) { max_sleep_millis = millis; } public void run() { long slept_for_millis; long memory_consumed; long bytes_per_second = 1000; init(); timestamp_previous = System.currentTimeMillis(); available_previous = memAvail(); slept_for_millis = sleep_millis; memory_consumed = available_previous - memAvail(); bytes_per_second = memory_consumed * 1000 / slept_for_millis; while (true) { try { priority = thread.getPriority(); float heap_total = memTotal(); float heap_avail = memAvail(); float heap_occupancy = (heap_total - heap_avail) / heap_total; debug("GC: before collection: " + (int)heap_avail + " bytes available out of " + (int)heap_total + ", occupancy = " + (int)(heap_occupancy * 100.0) + "%"); synchronized(this) { ++passes; collect(); this.notifyAll(); } synchronized(theFinalizer) { theFinalizer.notifyAll(); } heap_total = memTotal(); heap_avail = memAvail(); heap_occupancy = (heap_total - heap_avail) / heap_total; debug("GC: after collection: " + (int)heap_avail + " bytes available out of " + (int)heap_total + ", occupancy = " + (int)(heap_occupancy * 100.0) + "%"); // Calculate the duration of the next sleep based on the predicted // time to exhaust memory, multiplied by (1-occupancy)^2. Apply some // smoothing against sudden lengthening of the interval. int new_sleep_millis; if (bytes_per_second * slept_for_millis > 0 ) { float time_to_exhaustion = (available_previous - memory_consumed) / bytes_per_second; float one_minus_rho_squared = (1.0F - heap_occupancy) * (1.0F - heap_occupancy); new_sleep_millis = (int)(1000.0F * time_to_exhaustion * one_minus_rho_squared); debug("GC: available memory was " + available_previous + " bytes, less " + memory_consumed + " leaves " + (available_previous - memory_consumed)); debug("GC: predicted time to exhaustion = " + time_to_exhaustion + " seconds, multiplier = " + one_minus_rho_squared); } else { new_sleep_millis = max_sleep_millis; } sleep_millis = new_sleep_millis; if (sleep_millis < min_sleep_millis) { sleep_millis = min_sleep_millis; } if (sleep_millis > max_sleep_millis) { sleep_millis = max_sleep_millis; } priority = (int)(heap_occupancy * 4.0F) + 5; // CG 20040428 : WAS : 9.0F, + 1 thread.setPriority(priority); debug("GC: Will now sleep for " + sleep_millis + " milliseconds, priority is " + priority); timestamp_previous = System.currentTimeMillis(); available_previous = memAvail(); slept_for_millis = 0; while (slept_for_millis < sleep_millis && kicks == 0) { try { synchronized(this) { this.wait(sleep_millis); } } catch (InterruptedException ie) { } slept_for_millis = System.currentTimeMillis() - timestamp_previous; } if (slept_for_millis > 0) { memory_consumed = available_previous - memAvail(); debug("GC: Slept for " + slept_for_millis + " milliseconds"); debug("GC: During this time " + memory_consumed + " bytes of memory were consumed"); bytes_per_second = memory_consumed * 1000 / sleep_millis; // bytes_per_second = memory_consumed * 1000 / slept_for_millis; debug("GC: That makes " + bytes_per_second + " bytes per second"); if (bytes_per_second > 1000 && memory_consumed * 1000 > slept_for_millis) { debug("GC: At this rate we will run out of memory in " + ((available_previous - memory_consumed) / (memory_consumed * 1000 / slept_for_millis)) + " seconds"); } else { debug("GC: At this rate we will (almost) never run out of memory"); bytes_per_second = 1000; try { synchronized(this) { this.wait(max_sleep_millis); } } catch (InterruptedException ie) { } } } if (kicks > 0) { debug("GC: responding to kick"); // sleep_millis = sleep_millis / kicks; sleep_millis = min_sleep_millis; --kicks; synchronized(this) { ++passes; collect(); this.notifyAll(); } synchronized(theFinalizer) { theFinalizer.notifyAll(); } } } catch (Throwable t) { t.printStackTrace(); } } } static native long memTotal(); static native long memAvail(); }