1 /* 2 * Copyright (c) 2015, 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 * @library /testlibrary /test/lib 29 * @modules java.base/sun.misc 30 * java.management 31 * @build TestTargetSurvivorRatioFlag 32 * @run main ClassFileInstaller sun.hotspot.WhiteBox 33 * @run driver TestTargetSurvivorRatioFlag 34 */ 35 36 import jdk.test.lib.AllocationHelper; 37 import java.lang.management.GarbageCollectorMXBean; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.LinkedList; 41 import java.util.List; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import jdk.test.lib.HeapRegionUsageTool; 45 import sun.misc.Unsafe; 46 import jdk.test.lib.OutputAnalyzer; 47 import jdk.test.lib.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 "-XX:+UnlockDiagnosticVMOptions", 134 "-XX:+WhiteBoxAPI", 135 "-XX:+UseAdaptiveSizePolicy", 136 "-Xlog:gc+age=trace", 137 "-XX:MaxTenuringThreshold=" + MAX_TENURING_THRESHOLD, 138 "-XX:NewSize=" + MAX_NEW_SIZE, 139 "-XX:MaxNewSize=" + MAX_NEW_SIZE, 140 "-XX:InitialHeapSize=" + 2 * MAX_NEW_SIZE, 141 "-XX:MaxHeapSize=" + 2 * MAX_NEW_SIZE, 142 "-XX:SurvivorRatio=" + SURVIVOR_RATIO, 143 "-XX:TargetSurvivorRatio=" + ratio, 144 // For reducing variance of survivor size. 145 "-XX:TargetPLABWastePct=" + 1, 146 TargetSurvivorRatioVerifier.class.getName(), 147 Integer.toString(ratio) 148 ); 149 150 ProcessBuilder procBuilder = ProcessTools.createJavaProcessBuilder(vmOptions.toArray(new String[vmOptions.size()])); 151 OutputAnalyzer analyzer = new OutputAnalyzer(procBuilder.start()); 152 153 analyzer.shouldHaveExitValue(0); 154 155 String output = analyzer.getOutput(); 156 157 // Test avoids verification for parallel GC 158 if (!output.contains(UNSUPPORTED_GC)) { 159 // Two tests should be done - when actual ratio is lower than TargetSurvivorRatio 160 // and when it is higher. We chech that output contains results for exactly two tests. 161 List<Double> ratios = parseTestOutput(output); 162 163 if (ratios.size() != 2) { 164 System.out.println(output); 165 throw new RuntimeException("Expected number of ratios extraced for output is 2," 166 + " but " + ratios.size() + " ratios were extracted"); 167 } 168 169 // At the end of the first test survivor space usage ratio should lies between 170 // TargetSurvivorRatio and TargetSurvivorRatio - 2*DELTA 171 if (ratio < ratios.get(0) || ratio - ratios.get(0) > VARIANCE) { 172 System.out.println(output); 173 throw new RuntimeException("Survivor space usage ratio expected to be close to " 174 + ratio + ", but observed ratio is: " + ratios.get(0)); 175 } 176 177 // After second test survivor space should be almost empty. 178 if (ratios.get(1) > VARIANCE) { 179 System.out.println(output); 180 throw new RuntimeException("Survivor space expected to be empty due to " 181 + "TargetSurvivorRatio overlimit, however observed " 182 + "survivor space usage ratio is: " + ratios.get(1)); 183 } 184 } else { 185 System.out.println("Selected GC does not support TargetSurvivorRatio option."); 186 } 187 } 188 189 /** 190 * Parse output produced by TargetSurvivorRatioVerifier. 191 * 192 * @param output output obtained from TargetSurvivorRatioVerifier 193 * @return list of parsed test results, where each result is an actual 194 * survivor ratio after MaxTenuringThreshold minor GC cycles. 195 */ 196 public static List<Double> parseTestOutput(String output) { 197 List<Double> ratios = new LinkedList<Double>(); 198 String lines[] = output.split("[\n\r]"); 199 boolean testStarted = false; 200 long survivorSize = 0; 201 long survivorOccupancy = 0; 202 int gcCount = 0; 203 Pattern ageTableEntry = Pattern.compile(AGE_TABLE_ENTRY); 204 Pattern maxSurvivorSize = Pattern.compile(MAX_SURVIVOR_SIZE); 205 for (String line : lines) { 206 if (Pattern.matches(MAX_SURVIVOR_SIZE, line)) { 207 // We found estimated survivor space size 208 Matcher m = maxSurvivorSize.matcher(line); 209 m.find(); 210 survivorSize = Long.valueOf(m.group(1)); 211 } else if (line.contains(START_TEST) && !testStarted) { 212 // Start collecting test results 213 testStarted = true; 214 gcCount = 0; 215 } else if (testStarted) { 216 if (line.contains(TENURING_DISTRIBUTION)) { 217 // We found start of output emitted by -XX:+PrintTenuringDistribution 218 // If it is associated with "MaxTenuringThreshold" GC cycle, then it's 219 // time to report observed survivor usage ratio 220 gcCount++; 221 double survivorRatio = survivorOccupancy / (double) survivorSize; 222 if (gcCount == MAX_TENURING_THRESHOLD || gcCount == MAX_TENURING_THRESHOLD * 2) { 223 ratios.add(survivorRatio * 100.0); 224 testStarted = false; 225 } 226 survivorOccupancy = 0; 227 } else if (Pattern.matches(AGE_TABLE_ENTRY, line)) { 228 // Obtain survivor space usage from "total" age table log entry 229 Matcher m = ageTableEntry.matcher(line); 230 m.find(); 231 survivorOccupancy = Long.valueOf(m.group(3)); 232 } else if (line.contains(END_TEST)) { 233 // It is expected to find at least MaxTenuringThreshold GC events 234 // until test end 235 if (gcCount < MAX_TENURING_THRESHOLD) { 236 throw new RuntimeException("Observed " + gcCount + " GC events, " 237 + "while it is expected to see at least " 238 + MAX_TENURING_THRESHOLD); 239 } 240 testStarted = false; 241 } 242 } 243 } 244 return ratios; 245 } 246 247 public static class TargetSurvivorRatioVerifier { 248 249 static final WhiteBox wb = WhiteBox.getWhiteBox(); 250 static final Unsafe unsafe = Utils.getUnsafe(); 251 252 // Desired size of memory allocated at once 253 public static final int CHUNK_SIZE = 1024; 254 // Length of byte[] array that will have occupy CHUNK_SIZE bytes in heap 255 public static final int ARRAY_LENGTH = CHUNK_SIZE - Unsafe.ARRAY_BYTE_BASE_OFFSET; 256 257 public static void main(String args[]) throws Exception { 258 if (args.length != 1) { 259 throw new IllegalArgumentException("Expected 1 arg: <ratio>"); 260 } 261 if (GCTypes.YoungGCType.getYoungGCType() == GCTypes.YoungGCType.PSNew) { 262 System.out.println(UNSUPPORTED_GC); 263 return; 264 } 265 266 int ratio = Integer.valueOf(args[0]); 267 long maxSurvivorSize = getMaxSurvivorSize(); 268 System.out.println("Max survivor size: " + maxSurvivorSize); 269 270 allocateMemory(ratio - DELTA, maxSurvivorSize); 271 allocateMemory(ratio + DELTA, maxSurvivorSize); 272 } 273 274 /** 275 * Allocate (<b>ratio</b> * <b>maxSize</b> / 100) bytes of objects 276 * and force at least "MaxTenuringThreshold" minor GCs. 277 * 278 * @param ratio ratio used to calculate how many objects should be allocated 279 * @param maxSize estimated max survivor space size 280 */ 281 public static void allocateMemory(double ratio, long maxSize) throws Exception { 282 GarbageCollectorMXBean youngGCBean = GCTypes.YoungGCType.getYoungGCBean(); 283 long garbageSize = (long) (maxSize * (ratio / 100.0)); 284 int arrayLength = (int) (garbageSize / CHUNK_SIZE); 285 AllocationHelper allocator = new AllocationHelper(1, arrayLength, ARRAY_LENGTH, null); 286 287 System.out.println(START_TEST); 288 System.gc(); 289 final long initialGcId = youngGCBean.getCollectionCount(); 290 // allocate memory 291 allocator.allocateMemoryAndVerify(); 292 293 // force minor GC 294 while (youngGCBean.getCollectionCount() <= initialGcId + MAX_TENURING_THRESHOLD * 2) { 295 byte b[] = new byte[ARRAY_LENGTH]; 296 } 297 298 allocator.release(); 299 System.out.println(END_TEST); 300 } 301 302 /** 303 * Estimate max survivor space size. 304 * 305 * For non-G1 GC returns value reported by MemoryPoolMXBean 306 * associated with survivor space. 307 * For G1 GC return max number of survivor regions * region size. 308 * Number if survivor regions estimated from MaxNewSize and SurvivorRatio. 309 */ 310 public static long getMaxSurvivorSize() { 311 if (GCTypes.YoungGCType.getYoungGCType() == GCTypes.YoungGCType.G1) { 312 int youngLength = (int) Math.max(MAX_NEW_SIZE / wb.g1RegionSize(), 1); 313 return (long) Math.ceil(youngLength / (double) SURVIVOR_RATIO) * wb.g1RegionSize(); 314 } else { 315 return HeapRegionUsageTool.getSurvivorUsage().getMax(); 316 } 317 } 318 } 319 }