1 /*
  2  * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 package gc.arguments;
 25 
 26 /*
 27  * @test TestTargetSurvivorRatioFlag
 28  * @key gc
 29  * @summary Verify that option TargetSurvivorRatio affects survivor space occupancy after minor GC.
 30  * @requires vm.opt.ExplicitGCInvokesConcurrent != true
 31  * @requires vm.opt.UseJVMCICompiler != true
 32  * @requires vm.gc != "Z" & vm.gc != "Shenandoah"
 33  * @library /test/lib
 34  * @library /
 35  * @modules java.base/jdk.internal.misc
 36  *          java.management
 37  * @build sun.hotspot.WhiteBox
 38  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
 39  * @run driver gc.arguments.TestTargetSurvivorRatioFlag
 40  */
 41 
 42 import java.lang.management.GarbageCollectorMXBean;
 43 import java.util.Arrays;
 44 import java.util.Collections;
 45 import java.util.LinkedList;
 46 import java.util.List;
 47 import java.util.regex.Matcher;
 48 import java.util.regex.Pattern;
 49 import jdk.internal.misc.Unsafe;
 50 import jdk.test.lib.process.OutputAnalyzer;
 51 import jdk.test.lib.process.ProcessTools;
 52 import jdk.test.lib.Utils;
 53 import sun.hotspot.WhiteBox;
 54 
 55 /* In order to test that TargetSurvivorRatio affects survivor space occupancy
 56  * we setup fixed MaxTenuringThreshold and then verifying that if size of allocated
 57  * objects is lower than (survivor_size * TargetSurvivorRatio / 100) then objects
 58  * will stay in survivor space until MaxTenuringThreshold minor GC cycles.
 59  * If more than (survivor_size * TargetSurvivorRatio / 100) objects were allocated,
 60  * then we verify that after MaxTenuringThreshold minor GC cycles survivor space
 61  * is almost empty.
 62  */
 63 public class TestTargetSurvivorRatioFlag {
 64 
 65     public static final long M = 1024 * 1024;
 66 
 67     // VM option values
 68     public static final long MAX_NEW_SIZE = 40 * M;
 69     public static final int SURVIVOR_RATIO = 8;
 70     public static final int MAX_TENURING_THRESHOLD = 15;
 71 
 72     // Value used to estimate amount of memory that should be allocated
 73     // and placed in survivor space.
 74     public static final double DELTA = 0.25;
 75 
 76     // Max variance of observed ratio
 77     public static double VARIANCE = 1;
 78 
 79     // Messages used by debuggee
 80     public static final String UNSUPPORTED_GC = "Unsupported GC";
 81     public static final String START_TEST = "Start test";
 82     public static final String END_TEST = "End test";
 83 
 84     // Patterns used during log parsing
 85     public static final String TENURING_DISTRIBUTION = "Desired survivor size";
 86     public static final String AGE_TABLE_ENTRY = ".*-[\\s]+age[\\s]+([0-9]+):[\\s]+([0-9]+)[\\s]+bytes,[\\s]+([0-9]+)[\\s]+total";
 87     public static final String MAX_SURVIVOR_SIZE = "Max survivor size: ([0-9]+)";
 88 
 89     public static void main(String args[]) throws Exception {
 90 
 91         LinkedList<String> options = new LinkedList<>(Arrays.asList(Utils.getTestJavaOpts()));
 92 
 93         // Need to consider the effect of TargetPLABWastePct=1 for G1 GC
 94         if (options.contains("-XX:+UseG1GC")) {
 95             VARIANCE = 2;
 96         } else {
 97             VARIANCE = 1;
 98         }
 99 
100         negativeTest(-1, options);
101         negativeTest(101, options);
102 
103         positiveTest(20, options);
104         positiveTest(30, options);
105         positiveTest(55, options);
106         positiveTest(70, options);
107     }
108 
109     /**
110      * Verify that VM will fail to start with specified TargetSurvivorRatio
111      *
112      * @param ratio value of TargetSurvivorRatio
113      * @param options additional VM options
114      */
115     public static void negativeTest(int ratio, LinkedList<String> options) throws Exception {
116         LinkedList<String> vmOptions = new LinkedList<>(options);
117         vmOptions.add("-XX:TargetSurvivorRatio=" + ratio);
118         vmOptions.add("-version");
119 
120         ProcessBuilder procBuilder = ProcessTools.createJavaProcessBuilder(vmOptions.toArray(new String[vmOptions.size()]));
121         OutputAnalyzer analyzer = new OutputAnalyzer(procBuilder.start());
122 
123         analyzer.shouldHaveExitValue(1);
124         analyzer.shouldContain("Error: Could not create the Java Virtual Machine.");
125     }
126 
127     /**
128      * Verify that actual survivor space usage ratio conforms specified TargetSurvivorRatio
129      *
130      * @param ratio value of TargetSurvivorRatio
131      * @param options additional VM options
132      */
133     public static void positiveTest(int ratio, LinkedList<String> options) throws Exception {
134         LinkedList<String> vmOptions = new LinkedList<>(options);
135         Collections.addAll(vmOptions,
136                 "-Xbootclasspath/a:.",
137                 "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED",
138                 "-XX:+UnlockDiagnosticVMOptions",
139                 "-XX:+WhiteBoxAPI",
140                 "-XX:+UseAdaptiveSizePolicy",
141                 "-Xlog:gc+age=trace",
142                 "-XX:MaxTenuringThreshold=" + MAX_TENURING_THRESHOLD,
143                 "-XX:NewSize=" + MAX_NEW_SIZE,
144                 "-XX:MaxNewSize=" + MAX_NEW_SIZE,
145                 "-XX:InitialHeapSize=" + 2 * MAX_NEW_SIZE,
146                 "-XX:MaxHeapSize=" + 2 * MAX_NEW_SIZE,
147                 "-XX:SurvivorRatio=" + SURVIVOR_RATIO,
148                 "-XX:TargetSurvivorRatio=" + ratio,
149                 // For reducing variance of survivor size.
150                 "-XX:TargetPLABWastePct=" + 1,
151                 TargetSurvivorRatioVerifier.class.getName(),
152                 Integer.toString(ratio)
153         );
154 
155         ProcessBuilder procBuilder = ProcessTools.createJavaProcessBuilder(vmOptions.toArray(new String[vmOptions.size()]));
156         OutputAnalyzer analyzer = new OutputAnalyzer(procBuilder.start());
157 
158         analyzer.shouldHaveExitValue(0);
159 
160         String output = analyzer.getOutput();
161 
162         // Test avoids verification for parallel GC
163         if (!output.contains(UNSUPPORTED_GC)) {
164             // Two tests should be done - when actual ratio is lower than TargetSurvivorRatio
165             // and when it is higher. We chech that output contains results for exactly two tests.
166             List<Double> ratios = parseTestOutput(output);
167 
168             if (ratios.size() != 2) {
169                 System.out.println(output);
170                 throw new RuntimeException("Expected number of ratios extraced for output is 2,"
171                         + " but " + ratios.size() + " ratios were extracted");
172             }
173 
174             // At the end of the first test survivor space usage ratio should lies between
175             // TargetSurvivorRatio and TargetSurvivorRatio - 2*DELTA
176             if (ratio < ratios.get(0) || ratio - ratios.get(0) > VARIANCE) {
177                 System.out.println(output);
178                 throw new RuntimeException("Survivor space usage ratio expected to be close to "
179                         + ratio + ", but observed ratio is: " + ratios.get(0));
180             }
181 
182             // After second test survivor space should be almost empty.
183             if (ratios.get(1) > VARIANCE) {
184                 System.out.println(output);
185                 throw new RuntimeException("Survivor space expected to be empty due to "
186                         + "TargetSurvivorRatio overlimit, however observed "
187                         + "survivor space usage ratio is: " + ratios.get(1));
188             }
189         } else {
190             System.out.println("Selected GC does not support TargetSurvivorRatio option.");
191         }
192     }
193 
194     /**
195      * Parse output produced by TargetSurvivorRatioVerifier.
196      *
197      * @param output output obtained from TargetSurvivorRatioVerifier
198      * @return list of parsed test results, where each result is an actual
199      *         survivor ratio after MaxTenuringThreshold minor GC cycles.
200      */
201     public static List<Double> parseTestOutput(String output) {
202         List<Double> ratios = new LinkedList<Double>();
203         String lines[] = output.split("[\n\r]");
204         boolean testStarted = false;
205         long survivorSize = 0;
206         long survivorOccupancy = 0;
207         int gcCount = 0;
208         Pattern ageTableEntry = Pattern.compile(AGE_TABLE_ENTRY);
209         Pattern maxSurvivorSize = Pattern.compile(MAX_SURVIVOR_SIZE);
210         for (String line : lines) {
211             if (Pattern.matches(MAX_SURVIVOR_SIZE, line)) {
212                 // We found estimated survivor space size
213                 Matcher m = maxSurvivorSize.matcher(line);
214                 m.find();
215                 survivorSize = Long.valueOf(m.group(1));
216             } else if (line.contains(START_TEST) && !testStarted) {
217                 // Start collecting test results
218                 testStarted = true;
219                 gcCount = 0;
220             } else if (testStarted) {
221                 if (line.contains(TENURING_DISTRIBUTION)) {
222                     // We found start of output emitted by -XX:+PrintTenuringDistribution
223                     // If it is associated with "MaxTenuringThreshold" GC cycle, then it's
224                     // time to report observed survivor usage ratio
225                     gcCount++;
226                     double survivorRatio = survivorOccupancy / (double) survivorSize;
227                     if (gcCount == MAX_TENURING_THRESHOLD || gcCount == MAX_TENURING_THRESHOLD * 2) {
228                         ratios.add(survivorRatio * 100.0);
229                         testStarted = false;
230                     }
231                     survivorOccupancy = 0;
232                 } else if (Pattern.matches(AGE_TABLE_ENTRY, line)) {
233                     // Obtain survivor space usage from "total" age table log entry
234                     Matcher m = ageTableEntry.matcher(line);
235                     m.find();
236                     survivorOccupancy = Long.valueOf(m.group(3));
237                 } else if (line.contains(END_TEST)) {
238                     // It is expected to find at least MaxTenuringThreshold GC events
239                     // until test end
240                     if (gcCount < MAX_TENURING_THRESHOLD) {
241                         throw new RuntimeException("Observed " + gcCount + " GC events, "
242                                 + "while it is expected to see at least "
243                                 + MAX_TENURING_THRESHOLD);
244                     }
245                     testStarted = false;
246                 }
247             }
248         }
249         return ratios;
250     }
251 
252     public static class TargetSurvivorRatioVerifier {
253 
254         static final WhiteBox wb = WhiteBox.getWhiteBox();
255         static final Unsafe unsafe = Unsafe.getUnsafe();
256 
257         // Desired size of memory allocated at once
258         public static final int CHUNK_SIZE = 1024;
259         // Length of byte[] array that will have occupy CHUNK_SIZE bytes in heap
260         public static final int ARRAY_LENGTH = CHUNK_SIZE - Unsafe.ARRAY_BYTE_BASE_OFFSET;
261 
262         public static void main(String args[]) throws Exception {
263             if (args.length != 1) {
264                 throw new IllegalArgumentException("Expected 1 arg: <ratio>");
265             }
266             if (GCTypes.YoungGCType.getYoungGCType() == GCTypes.YoungGCType.PSNew) {
267                 System.out.println(UNSUPPORTED_GC);
268                 return;
269             }
270 
271             int ratio = Integer.valueOf(args[0]);
272             long maxSurvivorSize = getMaxSurvivorSize();
273             System.out.println("Max survivor size: " + maxSurvivorSize);
274 
275             allocateMemory(ratio - DELTA, maxSurvivorSize);
276             allocateMemory(ratio + DELTA, maxSurvivorSize);
277         }
278 
279         /**
280          * Allocate (<b>ratio</b> * <b>maxSize</b> / 100) bytes of objects
281          * and force at least "MaxTenuringThreshold" minor GCs.
282          *
283          * @param ratio ratio used to calculate how many objects should be allocated
284          * @param maxSize estimated max survivor space size
285          */
286         public static void allocateMemory(double ratio, long maxSize) throws Exception {
287             GarbageCollectorMXBean youngGCBean = GCTypes.YoungGCType.getYoungGCBean();
288             long garbageSize = (long) (maxSize * (ratio / 100.0));
289             int arrayLength = (int) (garbageSize / CHUNK_SIZE);
290             AllocationHelper allocator = new AllocationHelper(1, arrayLength, ARRAY_LENGTH, null);
291 
292             System.out.println(START_TEST);
293             System.gc();
294             final long initialGcId = youngGCBean.getCollectionCount();
295             // allocate memory
296             allocator.allocateMemoryAndVerify();
297 
298             // force minor GC
299             while (youngGCBean.getCollectionCount() <= initialGcId + MAX_TENURING_THRESHOLD * 2) {
300                 byte b[] = new byte[ARRAY_LENGTH];
301             }
302 
303             allocator.release();
304             System.out.println(END_TEST);
305         }
306 
307         /**
308          * Estimate max survivor space size.
309          *
310          * For non-G1 GC returns value reported by MemoryPoolMXBean
311          * associated with survivor space.
312          * For G1 GC return max number of survivor regions * region size.
313          * Number if survivor regions estimated from MaxNewSize and SurvivorRatio.
314          */
315         public static long getMaxSurvivorSize() {
316             if (GCTypes.YoungGCType.getYoungGCType() == GCTypes.YoungGCType.G1) {
317                 int youngLength = (int) Math.max(MAX_NEW_SIZE / wb.g1RegionSize(), 1);
318                 return (long) Math.ceil(youngLength / (double) SURVIVOR_RATIO) * wb.g1RegionSize();
319             } else {
320                 return HeapRegionUsageTool.getSurvivorUsage().getMax();
321             }
322         }
323     }
324 }