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 main ClassFileInstaller sun.hotspot.WhiteBox
35 * @run driver TestTargetSurvivorRatioFlag
36 */
37
38import java.lang.management.GarbageCollectorMXBean;
39import java.util.Arrays;
40import java.util.Collections;
41import java.util.LinkedList;
42import java.util.List;
43import java.util.regex.Matcher;
44import java.util.regex.Pattern;
45import jdk.internal.misc.Unsafe;
46import jdk.test.lib.process.OutputAnalyzer;
47import jdk.test.lib.process.ProcessTools;
48import jdk.test.lib.Utils;
49import 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 */
59public 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}
321