/* * Copyright 1996-2002 by Andruid Kerne. All rights reserved. CONFIDENTIAL. Use is subject to * license terms. */ package ecologylab.appframework; import ecologylab.generic.Debug; import ecologylab.generic.StringTools; /** * Utility routines related to memory management, used to watch the JVM's consumption of memory, and * to kick the garbage collector into action. */ public class Memory { // private static final int KICK_GC_COUNT = 5; private static final int KICK_GC_COUNT = 3; /** * Less than this many bytes of memory free means danger, baby. Currently set at 32M for PC and 0M for ANDROID */ public static final int DANGER_THRESHOLD = (PropertiesAndDirectories.os() == PropertiesAndDirectories.ANDROID) ? 0 : 32 * 1024 * 1024; public static final int RECLAIM_THRESHOLD = 2 * DANGER_THRESHOLD; static final Runtime RUNTIME = Runtime.getRuntime(); /** * Number of times we've called gc(). */ public static int gcCount; public static boolean isMicroshaftVM; static long gcTimeStamp; static StringBuffer buffy = new StringBuffer(256); static final int GC_MAX_DELTA_T = 30 * 1000; // 30 seconds /** * Prod the garbage collector, and print a message about memory status. */ public static void reclaim() { reclaim(""); } /** * Prod the garbage collector, and print a message about memory status. * * @param s * Part of the message to be printed, to identify call site. */ public static synchronized void reclaim(String s) { try { StringTools.clear(buffy); buffy.append("\nMemory.reclaim(").append(Thread.currentThread().getName()).append(s) .append("):\n\t").append(getFreeMemoryInK()); reclaimQuiet(); buffy.append(" -> ").append(usage()).append("\t #").append(gcCount).append("\n"); Debug.println(buffy); } catch (Exception e) { e.printStackTrace(); } } public static synchronized void reclaimQuiet() { gcTimeStamp = System.currentTimeMillis(); System.gc(); gcCount++; } private static final Object MEMORY_RECLAIM_LOCK = new Object(); /** * Try to reclaim memory if it seems to be in low supply. Kicks the garbage collector, perhaps * repeatedly. Does this even though GC is supposed to be automatic. * <p/> * Also maintains the current time stamp to the one in which the garbage collector was last * kicked. To avoid maxing out the CPU calling GC repeatedly, will not kick GC again if the last * time was recent. (Currently, recent means within the last 30 seconds.) * * @return true if memory status is in danger and more agressive measures are called for. false if * everything is fine, and the caller can proceed to perform operations that use lots of * memory, as needed. * */ public static boolean reclaimIfLow() { synchronized (MEMORY_RECLAIM_LOCK) { // this lock pushes memory check (and the expensive reclaim part) into one thread at a time. long now = System.currentTimeMillis(); long deltaT = now - gcTimeStamp; if (deltaT >= GC_MAX_DELTA_T) { for (int i = 0; i != KICK_GC_COUNT; i++) { if (RUNTIME.freeMemory() < RECLAIM_THRESHOLD) reclaim(); else break; } } } return RUNTIME.freeMemory() < DANGER_THRESHOLD; } public static void recover(Throwable throwable, String msg) { Memory.reclaimQuiet(); Debug.println("Memory.recover()"); if (msg != null) Memory.reclaim(msg); throwable.printStackTrace(); // Thread.dumpStack(); Debug.println(Memory.threads()); Debug.println(""); } public static String usage() { return getFreeMemoryInK() + " free of " + K(RUNTIME.totalMemory()); } public static String K(long numBytes) { return (numBytes / 1024) + "K"; } public static String threads() { Thread current = Thread.currentThread(); int count = Thread.activeCount(); Thread[] threads = new Thread[count]; Thread.enumerate(threads); String result = count + " Threads ACTIVE\n"; for (int i = 0; i != count; i++) { Thread t = threads[i]; if (t != null) { // String alive = t.isAlive() ? "alive" : "dead"; result += threads[i].getName() + "\n"; } } return result; } static int outOfMemoryCount; static boolean processingOutOfMemory; static final int ENOUGH_OUT_OF_MEMORY_CALLS = 10; /** * @return true if its time to give up! */ public static boolean outOfMemory(Throwable throwable) { if (outOfMemoryCount >= ENOUGH_OUT_OF_MEMORY_CALLS) return true; if (!processingOutOfMemory) { // dont bother locking, cause we're too desparate processingOutOfMemory = true; outOfMemoryCount++; Memory.recover(throwable, null); processingOutOfMemory = false; } return false; } public static long getFreeMemoryInBytes() { return RUNTIME.freeMemory(); } public static String getFreeMemoryInK() { return K(getFreeMemoryInBytes()); } }