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