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}