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