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 }