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