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 }