001/* 002 * This file is part of the Jikes RVM project (http://jikesrvm.org). 003 * 004 * This file is licensed to You under the Eclipse Public License (EPL); 005 * You may not use this file except in compliance with the License. You 006 * may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/eclipse-1.0.php 009 * 010 * See the COPYRIGHT.txt file distributed with this work for information 011 * regarding copyright ownership. 012 */ 013package org.mmtk.utility.heap; 014 015import static org.mmtk.utility.Constants.*; 016 017import org.mmtk.plan.Plan; 018import org.mmtk.utility.*; 019import org.mmtk.utility.options.Options; 020 021import org.mmtk.vm.VM; 022 023import org.vmmagic.pragma.*; 024import org.vmmagic.unboxed.*; 025 026/** 027 * This class is responsible for growing and shrinking the 028 * heap size by observing heap utilization and GC load. 029 */ 030@Uninterruptible public abstract class HeapGrowthManager { 031 032 /** 033 * The initial heap size (-Xms) in bytes 034 */ 035 private static Extent initialHeapSize; 036 037 /** 038 * The maximum heap size (-Xms) in bytes 039 */ 040 private static Extent maxHeapSize; 041 042 /** 043 * The current heap size in bytes 044 */ 045 private static Extent currentHeapSize; 046 047 048 private static final double[][] generationalFunction = {{0.00, 0.00, 0.10, 0.30, 0.60, 0.80, 1.00}, 049 { 0.00, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, 050 { 0.01, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, 051 { 0.02, 0.95, 0.95, 1.00, 1.00, 1.00, 1.00 }, 052 { 0.07, 1.00, 1.00, 1.10, 1.15, 1.20, 1.20 }, 053 { 0.15, 1.00, 1.00, 1.20, 1.25, 1.35, 1.30 }, 054 { 0.40, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 }, 055 { 1.00, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 } }; 056 057 private static final double[][] nongenerationalFunction = {{0.00, 0.00, 0.10, 0.30, 0.60, 0.80, 1.00}, 058 { 0.00, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, 059 { 0.02, 0.90, 0.90, 0.95, 1.00, 1.00, 1.00 }, 060 { 0.05, 0.95, 0.95, 1.00, 1.00, 1.00, 1.00 }, 061 { 0.15, 1.00, 1.00, 1.10, 1.15, 1.20, 1.20 }, 062 { 0.30, 1.00, 1.00, 1.20, 1.25, 1.35, 1.30 }, 063 { 0.50, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 }, 064 { 1.00, 1.00, 1.00, 1.25, 1.30, 1.50, 1.50 } }; 065 066 /** 067 * An encoding of the function used to manage heap size. 068 * The xaxis represents the live ratio at the end of a major collection. 069 * The yaxis represents the GC load (GC time/total time). 070 * The interior of the matrix represents a ratio to shrink or grow 071 * the heap for a given pair of live ratio and GC load. 072 * The constraints on the matrix are: 073 * <ul> 074 * <li> function[0][0] is ignored. 075 * <li> All numbers in the first row must monotonically increase and 076 * must be in the range from 0 to 1 inclusive.</li> 077 * <li> All numbers in the first column must monotonically increase 078 * and must be in the range from 0 to 1 inclusive.</li> 079 * <li> There must be 0 and 1 values specified in both dimensions. 080 * <li> For all interior points in the matrix, the value must be 081 * greater than the liveRatio for that column.</li> 082 * </ul> 083 */ 084 private static final double[][] function = 085 VM.activePlan.constraints().generational() ? generationalFunction : nongenerationalFunction; 086 087 private static long endLastMajorGC; 088 private static double accumulatedGCTime; 089 090 /** 091 * Initialize heap size parameters and the mechanisms 092 * used to adaptively change heap size. 093 * 094 * @param initial the initial heap size 095 * @param max the maximum heap size 096 */ 097 public static void boot(Extent initial, Extent max) { 098 initialHeapSize = initial; 099 maxHeapSize = max; 100 if (initialHeapSize.GT(maxHeapSize)) 101 maxHeapSize = initialHeapSize; 102 currentHeapSize = initialHeapSize; 103 VM.events.heapSizeChanged(currentHeapSize); 104 if (VM.VERIFY_ASSERTIONS) sanityCheck(); 105 endLastMajorGC = VM.statistics.nanoTime(); 106 } 107 108 /** 109 * @return the current heap size in bytes 110 */ 111 public static Extent getCurrentHeapSize() { 112 return currentHeapSize; 113 } 114 115 /** 116 * Return the max heap size in bytes (as set by -Xmx). 117 * 118 * @return The max heap size in bytes (as set by -Xmx). 119 */ 120 public static Extent getMaxHeapSize() { 121 return maxHeapSize; 122 } 123 124 /** 125 * Return the initial heap size in bytes (as set by -Xms). 126 * 127 * @return The initial heap size in bytes (as set by -Xms). 128 */ 129 public static Extent getInitialHeapSize() { 130 return initialHeapSize; 131 } 132 133 /** 134 * Forcibly grow the heap by the given number of bytes. 135 * Used to provide headroom when handling an OutOfMemory 136 * situation. 137 * @param size number of bytes to grow the heap 138 */ 139 public static void overrideGrowHeapSize(Extent size) { 140 currentHeapSize = currentHeapSize.plus(size); 141 VM.events.heapSizeChanged(currentHeapSize); 142 } 143 144 /** 145 * Record the time taken by the current GC; 146 * used to compute gc load, one of the inputs 147 * into the heap size management function 148 * 149 * @param time number of time taking for current GC, in 150 * milliseconds 151 */ 152 public static void recordGCTime(double time) { 153 accumulatedGCTime += time; 154 } 155 156 /** 157 * Reset timers used to compute gc load 158 */ 159 public static void reset() { 160 endLastMajorGC = VM.statistics.nanoTime(); 161 accumulatedGCTime = 0; 162 } 163 164 /** 165 * Decide how to grow/shrink the heap to respond 166 * to application's memory usage. 167 * @return {@code true} if heap size was changed, {@code false} otherwise 168 */ 169 public static boolean considerHeapSize() { 170 Extent oldSize = currentHeapSize; 171 Extent reserved = Plan.reservedMemory(); 172 double liveRatio = reserved.toLong() / ((double) currentHeapSize.toLong()); 173 double ratio = computeHeapChangeRatio(liveRatio); 174 Extent newSize = Word.fromIntSignExtend((int)(ratio * (oldSize.toLong() >> LOG_BYTES_IN_MBYTE))).lsh(LOG_BYTES_IN_MBYTE).toExtent(); // do arith in MB to avoid overflow 175 if (newSize.LT(reserved)) newSize = reserved; 176 newSize = newSize.plus(BYTES_IN_MBYTE - 1).toWord().rshl(LOG_BYTES_IN_MBYTE).lsh(LOG_BYTES_IN_MBYTE).toExtent(); // round to next megabyte 177 if (newSize.GT(maxHeapSize)) newSize = maxHeapSize; 178 if (newSize.NE(oldSize) && newSize.GT(Extent.zero())) { 179 // Heap size is going to change 180 currentHeapSize = newSize; 181 if (Options.verbose.getValue() >= 2) { 182 Log.write("GC Message: Heap changed from "); Log.writeDec(oldSize.toWord().rshl(LOG_BYTES_IN_KBYTE)); 183 Log.write("KB to "); Log.writeDec(newSize.toWord().rshl(LOG_BYTES_IN_KBYTE)); 184 Log.writeln("KB"); 185 } 186 VM.events.heapSizeChanged(currentHeapSize); 187 return true; 188 } else { 189 return false; 190 } 191 } 192 193 private static double computeHeapChangeRatio(double liveRatio) { 194 // (1) compute GC load. 195 long totalNanos = VM.statistics.nanoTime() - endLastMajorGC; 196 double totalTime = VM.statistics.nanosToMillis(totalNanos); 197 double gcLoad = accumulatedGCTime / totalTime; 198 199 if (liveRatio > 1) { 200 // Perhaps indicates bad bookkeeping in MMTk? 201 Log.write("GCWarning: Live ratio greater than 1: "); 202 Log.writeln(liveRatio); 203 liveRatio = 1; 204 } 205 if (gcLoad > 1) { 206 if (gcLoad > 1.0001) { 207 Log.write("GC Error: GC load was greater than 1!! "); 208 Log.writeln(gcLoad); 209 Log.write("GC Error:\ttotal time (ms) "); Log.writeln(totalTime); 210 Log.write("GC Error:\tgc time (ms) "); Log.writeln(accumulatedGCTime); 211 } 212 gcLoad = 1; 213 } 214 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio >= 0); 215 if (VM.VERIFY_ASSERTIONS && gcLoad < -0.0) { 216 Log.write("gcLoad computed to be "); Log.writeln(gcLoad); 217 Log.write("\taccumulateGCTime was (ms) "); Log.writeln(accumulatedGCTime); 218 Log.write("\ttotalTime was (ms) "); Log.writeln(totalTime); 219 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(false); 220 } 221 222 if (Options.verbose.getValue() > 2) { 223 Log.write("Live ratio "); Log.writeln(liveRatio); 224 Log.write("GCLoad "); Log.writeln(gcLoad); 225 } 226 227 // (2) Find the 4 points surrounding gcLoad and liveRatio 228 int liveRatioUnder = 1; 229 int liveRatioAbove = function[0].length - 1; 230 int gcLoadUnder = 1; 231 int gcLoadAbove = function.length - 1; 232 if (liveRatio >= 1.0) { 233 // liveRatio has maxed out 234 liveRatioUnder = liveRatioAbove; 235 } else { 236 while (true) { 237 if (function[0][liveRatioUnder + 1] > liveRatio) break; 238 liveRatioUnder++; 239 } 240 while (true) { 241 if (function[0][liveRatioAbove - 1] <= liveRatio) break; 242 liveRatioAbove--; 243 } 244 } 245 if (gcLoad >= 1.0) { 246 // gcRatio has maxed out 247 gcLoadUnder = gcLoadAbove; 248 } else { 249 while (true) { 250 if (function[gcLoadUnder + 1][0] > gcLoad) break; 251 gcLoadUnder++; 252 } 253 while (true) { 254 if (function[gcLoadAbove - 1][0] <= gcLoad) break; 255 gcLoadAbove--; 256 } 257 } 258 259 // (3) Compute the heap change ratio 260 double factor = function[gcLoadUnder][liveRatioUnder]; 261 if (liveRatioUnder != liveRatioAbove) { 262 // interpolate for liveRatio values in between two specified values in function table 263 double liveRatioFraction = 264 (liveRatio - function[0][liveRatioUnder]) / 265 (function[0][liveRatioAbove] - function[0][liveRatioUnder]); 266 double liveRatioDelta = 267 function[gcLoadUnder][liveRatioAbove] - function[gcLoadUnder][liveRatioUnder]; 268 factor += (liveRatioFraction * liveRatioDelta); 269 } 270 if (gcLoadUnder != gcLoadAbove) { 271 // interpolate for gcLoad values in between two specified values in function table 272 double gcLoadFraction = 273 (gcLoad - function[gcLoadUnder][0]) / 274 (function[gcLoadAbove][0] - function[gcLoadUnder][0]); 275 double gcLoadDelta = 276 function[gcLoadAbove][liveRatioUnder] - function[gcLoadUnder][liveRatioUnder]; 277 factor += (gcLoadFraction * gcLoadDelta); 278 } 279 if (Options.verbose.getValue() > 2) { 280 Log.write("Heap adjustment factor is "); 281 Log.writeln(factor); 282 } 283 return factor; 284 } 285 286 /** 287 * Check that function satisfies the invariants 288 */ 289 private static void sanityCheck() { 290 // Check live ratio 291 double[] liveRatio = function[0]; 292 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio[1] == 0); 293 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio[liveRatio.length - 1] == 1); 294 for (int i = 2; i < liveRatio.length; i++) { 295 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(liveRatio[i - 1] < liveRatio[i]); 296 for (int j = 1; j < function.length; j++) { 297 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[j][i] >= 1 || function[j][i] > liveRatio[i]); 298 } 299 } 300 301 // Check GC load 302 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[1][0] == 0); 303 int len = function.length; 304 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[len - 1][0] == 1); 305 for (int i = 2; i < len; i++) { 306 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[i - 1][0] < function[i][0]); 307 } 308 309 // Check that we have a rectangular matrix 310 for (int i = 1; i < function.length; i++) { 311 if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(function[i - 1].length == function[i].length); 312 } 313 } 314}