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