/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.mmtk.utility.heap; import org.mmtk.plan.Plan; import org.mmtk.utility.*; import org.mmtk.utility.options.Options; import org.mmtk.vm.VM; import org.vmmagic.pragma.*; import org.vmmagic.unboxed.*; /** * This class is responsible for growing and shrinking the * heap size by observing heap utilization and GC load. */ @Uninterruptible public abstract class HeapGrowthManager implements Constants { /** * The initial heap size (-Xms) in bytes */ private static Extent initialHeapSize; /** * The maximum heap size (-Xms) in bytes */ private static Extent maxHeapSize; /** * The current heap size in bytes */ private static Extent currentHeapSize; private static final double[][] generationalFunction = {{0.00, 0.00, 0.10, 0.30, 0.60, 0.80, 1.00}, { 0.00, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, { 0.01, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, { 0.02, 0.95, 0.95, 1.00, 1.00, 1.00, 1.00 }, { 0.07, 1.00, 1.00, 1.10, 1.15, 1.20, 1.20 }, { 0.15, 1.00, 1.00, 1.20, 1.25, 1.35, 1.30 }, { 0.40, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 }, { 1.00, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 } }; private static final double[][] nongenerationalFunction = {{0.00, 0.00, 0.10, 0.30, 0.60, 0.80, 1.00}, { 0.00, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, { 0.02, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, { 0.05, 0.95, 0.95, 1.00, 1.00, 1.00, 1.00 }, { 0.15, 1.00, 1.00, 1.10, 1.15, 1.20, 1.20 }, { 0.30, 1.00, 1.00, 1.20, 1.25, 1.35, 1.30 }, { 0.50, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 }, { 1.00, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 } }; /** * An encoding of the function used to manage heap size. * The xaxis represents the live ratio at the end of a major collection. * The yaxis represents the GC load (GC time/total time). * The interior of the matrix represents a ratio to shrink or grow * the heap for a given pair of live ratio and GC load. * The constraints on the matrix are: * <ul> * <li> function[0][0] is ignored. * <li> All numbers in the first row must monotonically increase and * must be in the range from 0 to 1 inclusive.</li> * <li> All numbers in the first column must monotonically increase * and must be in the range from 0 to 1 inclusive.</li> * <li> There must be 0 and 1 values specified in both dimensions. * <li> For all interior points in the matrix, the value must be * greater than the liveRatio for that column.</li> * </ul> */ private static final double[][] function = VM.activePlan.constraints().generational() ? generationalFunction : nongenerationalFunction; private static long endLastMajorGC; private static double accumulatedGCTime; /** * Initialize heap size parameters and the mechanisms * used to adaptively change heap size. */ public static void boot(Extent initial, Extent max) { initialHeapSize = initial; maxHeapSize = max; if (initialHeapSize.GT(maxHeapSize)) maxHeapSize = initialHeapSize; currentHeapSize = initialHeapSize; VM.events.heapSizeChanged(currentHeapSize); if (VM.VERIFY_ASSERTIONS) sanityCheck(); endLastMajorGC = VM.statistics.nanoTime(); } /** * @return the current heap size in bytes */ public static Extent getCurrentHeapSize() { return currentHeapSize; } /** * Return the max heap size in bytes (as set by -Xmx). * * @return The max heap size in bytes (as set by -Xmx). */ public static Extent getMaxHeapSize() { return maxHeapSize; } /** * Return the initial heap size in bytes (as set by -Xms). * * @return The initial heap size in bytes (as set by -Xms). */ public static Extent getInitialHeapSize() { return initialHeapSize; } /** * Forcibly grow the heap by the given number of bytes. * Used to provide headroom when handling an OutOfMemory * situation. * @param size number of bytes to grow the heap */ public static void overrideGrowHeapSize(Extent size) { currentHeapSize = currentHeapSize.plus(size); VM.events.heapSizeChanged(currentHeapSize); } /** * Record the time taken by the current GC; * used to compute gc load, one of the inputs * into the heap size management function */ public static void recordGCTime(double time) { accumulatedGCTime += time; } /** * Reset timers used to compute gc load */ public static void reset() { endLastMajorGC = VM.statistics.nanoTime(); accumulatedGCTime = 0; } /** * Decide how to grow/shrink the heap to respond * to application's memory usage. * @return true if heap size was changed, false otherwise */ public static boolean considerHeapSize() { Extent oldSize = currentHeapSize; Extent reserved = Plan.reservedMemory(); double liveRatio = reserved.toLong() / ((double) currentHeapSize.toLong()); double ratio = computeHeapChangeRatio(liveRatio); Extent newSize = Word.fromIntSignExtend((int)(ratio * (double) (oldSize.toLong()>>LOG_BYTES_IN_MBYTE))).lsh(LOG_BYTES_IN_MBYTE).toExtent(); // do arith in MB to avoid overflow if (newSize.LT(reserved)) newSize = reserved; newSize = newSize.plus(BYTES_IN_MBYTE - 1).toWord().rshl(LOG_BYTES_IN_MBYTE).lsh(LOG_BYTES_IN_MBYTE).toExtent(); // round to next megabyte if (newSize.GT(maxHeapSize)) newSize = maxHeapSize; if (newSize.NE(oldSize) && newSize.GT(Extent.zero())) { // Heap size is going to change currentHeapSize = newSize; if (Options.verbose.getValue() >= 2) { Log.write("GC Message: Heap changed from "); Log.writeDec(oldSize.toWord().rshl(LOG_BYTES_IN_KBYTE)); Log.write("KB to "); Log.writeDec(newSize.toWord().rshl(LOG_BYTES_IN_KBYTE)); Log.writeln("KB"); } VM.events.heapSizeChanged(currentHeapSize); return true; } else { return false; } } private static double computeHeapChangeRatio(double liveRatio) { // (1) compute GC load. long totalNanos = VM.statistics.nanoTime() - endLastMajorGC; double totalTime = VM.statistics.nanosToMillis(totalNanos); double gcLoad = accumulatedGCTime / totalTime; if (liveRatio > 1) { // Perhaps indicates bad bookkeeping in MMTk? Log.write("GCWarning: Live ratio greater than 1: "); Log.writeln(liveRatio); liveRatio = 1; } if (gcLoad > 1) { if (gcLoad > 1.0001) { Log.write("GC Error: GC load was greater than 1!! "); Log.writeln(gcLoad); Log.write("GC Error:\ttotal time (ms) "); Log.writeln(totalTime); Log.write("GC Error:\tgc time (ms) "); Log.writeln(accumulatedGCTime); } gcLoad = 1; } if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio >= 0); if (VM.VERIFY_ASSERTIONS && gcLoad < -0.0) { Log.write("gcLoad computed to be "); Log.writeln(gcLoad); Log.write("\taccumulateGCTime was (ms) "); Log.writeln(accumulatedGCTime); Log.write("\ttotalTime was (ms) "); Log.writeln(totalTime); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(false); } if (Options.verbose.getValue() > 2) { Log.write("Live ratio "); Log.writeln(liveRatio); Log.write("GCLoad "); Log.writeln(gcLoad); } // (2) Find the 4 points surrounding gcLoad and liveRatio int liveRatioUnder = 1; int liveRatioAbove = function[0].length - 1; int gcLoadUnder = 1; int gcLoadAbove = function.length - 1; while (true) { if (function[0][liveRatioUnder+1] >= liveRatio) break; liveRatioUnder++; } while (true) { if (function[0][liveRatioAbove-1] <= liveRatio) break; liveRatioAbove--; } while (true) { if (function[gcLoadUnder+1][0] >= gcLoad) break; gcLoadUnder++; } while (true) { if (function[gcLoadAbove-1][0] <= gcLoad) break; gcLoadAbove--; } // (3) Compute the heap change ratio double factor = function[gcLoadUnder][liveRatioUnder]; double liveRatioFraction = (liveRatio - function[0][liveRatioUnder]) / (function[0][liveRatioAbove] - function[0][liveRatioUnder]); double liveRatioDelta = function[gcLoadUnder][liveRatioAbove] - function[gcLoadUnder][liveRatioUnder]; factor += (liveRatioFraction * liveRatioDelta); double gcLoadFraction = (gcLoad - function[gcLoadUnder][0]) / (function[gcLoadAbove][0] - function[gcLoadUnder][0]); double gcLoadDelta = function[gcLoadAbove][liveRatioUnder] - function[gcLoadUnder][liveRatioUnder]; factor += (gcLoadFraction * gcLoadDelta); if (Options.verbose.getValue() > 2) { Log.write("Heap adjustment factor is "); Log.writeln(factor); } return factor; } /** * Check that function satisfies the invariants */ private static void sanityCheck() { // Check live ratio double[] liveRatio = function[0]; if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio[1] == 0); if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio[liveRatio.length-1] == 1); for (int i = 2; i < liveRatio.length; i++) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio[i-1] < liveRatio[i]); for (int j = 1; j < function.length; j++) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[j][i] >= 1 || function[j][i] > liveRatio[i]); } } // Check GC load if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[1][0] == 0); int len = function.length; if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[len-1][0] == 1); for (int i = 2; i < len; i++) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[i-1][0] < function[i][0]); } // Check that we have a rectangular matrix for (int i = 1; i < function.length; i++) { if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[i-1].length == function[i].length); } } }