1 /*
2  * Copyright (c) 2016, 2020, 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 package gc.arguments;
25 
26 /*
27  * @test TestMaxMinHeapFreeRatioFlags
28  * @summary Verify that heap size changes according to max and min heap free ratios.
29  * @requires vm.gc != "Z" & vm.gc != "Shenandoah"
30  * @library /test/lib
31  * @library /
32  * @modules java.base/jdk.internal.misc
33  *          java.management
34  * @run driver/timeout=240 gc.arguments.TestMaxMinHeapFreeRatioFlags
35  */
36 
37 import java.util.LinkedList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import jdk.test.lib.process.OutputAnalyzer;
41 import jdk.test.lib.process.ProcessTools;
42 import jdk.test.lib.Utils;
43 import jdk.internal.misc.Unsafe;
44 
45 public class TestMaxMinHeapFreeRatioFlags {
46 
47     public static final long M = 1024 * 1024;
48     public static final long MAX_HEAP_SIZE = 200 * M;
49     public static final long HEAP_SIZE = 10 * M;
50     public static final long MAX_NEW_SIZE = 20 * M;
51     public static final long NEW_SIZE = 5 * M;
52 
main(String args[])53     public static void main(String args[]) throws Exception {
54         LinkedList<String> options = new LinkedList<>(
55                 Arrays.asList(Utils.getFilteredTestJavaOpts("-XX:[^ ]*HeapFreeRatio","-XX:\\+ExplicitGCInvokesConcurrent"))
56         );
57 
58         negativeTest(20, false, 10, true, options);
59         negativeTest(100, true, 0, false, options);
60         negativeTest(101, false, 50, false, options);
61         negativeTest(49, true, 102, true, options);
62         negativeTest(-1, false, 50, false, options);
63         negativeTest(50, true, -1, true, options);
64 
65         positiveTest(10, false, 90, false, true, options);
66         positiveTest(10, true, 80, false, true, options);
67         positiveTest(20, false, 70, true, true, options);
68         positiveTest(25, true, 65, true, true, options);
69         positiveTest(40, false, 50, false, true, options);
70     }
71 
72     /**
73      * Verify that heap size will be changed to conform
74      * min and max heap free ratios.
75      *
76      * @param minRatio value of MinHeapFreeRatio option
77      * @param useXminf used Xminf option instead of MinHeapFreeRatio
78      * @param maxRatio value of MaxHeapFreeRatio option
79      * @param useXmaxf used Xmaxf option instead of MaxHeapFreeRatio
80      * @param options additional options for JVM
81      */
positiveTest(int minRatio, boolean useXminf, int maxRatio, boolean useXmaxf, boolean shrinkHeapInSteps, LinkedList<String> options)82     public static void positiveTest(int minRatio, boolean useXminf,
83             int maxRatio, boolean useXmaxf, boolean shrinkHeapInSteps,
84             LinkedList<String> options) throws Exception {
85 
86         LinkedList<String> vmOptions = new LinkedList<>(options);
87         Collections.addAll(vmOptions,
88                 (useXminf ? "-Xminf" + minRatio / 100.0 : "-XX:MinHeapFreeRatio=" + minRatio),
89                 (useXmaxf ? "-Xmaxf" + maxRatio / 100.0 : "-XX:MaxHeapFreeRatio=" + maxRatio),
90                 "-Xmx" + MAX_HEAP_SIZE,
91                 "-Xms" + HEAP_SIZE,
92                 "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED",
93                 "-XX:NewSize=" + NEW_SIZE,
94                 "-XX:MaxNewSize=" + MAX_NEW_SIZE,
95                 "-XX:" + (shrinkHeapInSteps ? '+' : '-') + "ShrinkHeapInSteps",
96                 RatioVerifier.class.getName(),
97                 Integer.toString(minRatio),
98                 Integer.toString(maxRatio),
99                 Boolean.toString(shrinkHeapInSteps)
100         );
101 
102         ProcessBuilder procBuilder = GCArguments.createJavaProcessBuilder(vmOptions);
103         OutputAnalyzer analyzer = new OutputAnalyzer(procBuilder.start());
104         analyzer.shouldHaveExitValue(0);
105     }
106 
107     /**
108      * Verify that VM will fail to start with specified ratios.
109      *
110      * @param minRatio value of MinHeapFreeRatio option
111      * @param useXminf used Xminf option instead of MinHeapFreeRatio
112      * @param maxRatio value of MaxHeapFreeRatio option
113      * @param useXmaxf used Xmaxf option instead of MaxHeapFreeRatio
114      * @param options additional options for JVM
115      */
negativeTest(int minRatio, boolean useXminf, int maxRatio, boolean useXmaxf, LinkedList<String> options)116     public static void negativeTest(int minRatio, boolean useXminf,
117             int maxRatio, boolean useXmaxf,
118             LinkedList<String> options) throws Exception {
119 
120         LinkedList<String> vmOptions = new LinkedList<>(options);
121         Collections.addAll(vmOptions,
122                 (useXminf ? "-Xminf" + minRatio / 100.0 : "-XX:MinHeapFreeRatio=" + minRatio),
123                 (useXmaxf ? "-Xmaxf" + maxRatio / 100.0 : "-XX:MaxHeapFreeRatio=" + maxRatio),
124                 "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED",
125                 "-version"
126         );
127         ProcessBuilder procBuilder = GCArguments.createJavaProcessBuilder(vmOptions);
128         OutputAnalyzer analyzer = new OutputAnalyzer(procBuilder.start());
129         analyzer.shouldHaveExitValue(1);
130         analyzer.shouldContain("Error: Could not create the Java Virtual Machine.");
131     }
132 
133     /**
134      * RatioVerifier will be executed in the tested VM.
135      * It will check that real heap usage after collection lies between MinHeapFreeRatio and MaxHeapFreeRatio.
136      */
137     public static class RatioVerifier {
138 
139         private static final Unsafe unsafe = Unsafe.getUnsafe();
140 
141         // Size of byte array that will be allocated
142         public static final int CHUNK_SIZE = 1024;
143         // Length of byte array, that will be added to "garbage" list.
144         public static final int ARRAY_LENGTH = CHUNK_SIZE - Unsafe.ARRAY_BYTE_BASE_OFFSET;
145         // Amount of tries to force heap shrinking/expansion using GC
146         public static final int GC_TRIES = 10;
147 
148         // Value that will be added/substracted from expected min/max heap free ratio
149         // during memory allocation to make sure that specified limit will be exceeded.
150         public static final double OVERLOAD = 0.05;
151         // Acceptable heap free ratio limit exceedance: verification will fail if
152         // actual ratio is lower than expected min heap free ratio - VARIANCE or
153         // higher than expected max heap free ratio + VARIANCE.
154         public static final double VARIANCE = 0.025;
155 
156         public static LinkedList<Object> garbage = new LinkedList<>();
157 
main(String args[])158         public static void main(String args[]) throws Exception {
159             if (args.length != 3) {
160                 throw new IllegalArgumentException("Expected 3 args: <minRatio> <maxRatio> <shrinkHeapInSteps>");
161             }
162             if (GCTypes.OldGCType.getOldGCType() == GCTypes.OldGCType.PSOld ||
163                 GCTypes.OldGCType.getOldGCType() == GCTypes.OldGCType.G1) {
164                 System.out.println("Test is not applicable to parallel full GCs");
165                 return;
166             }
167 
168             double minRatio = Integer.valueOf(args[0]) / 100.0;
169             double maxRatio = Integer.valueOf(args[1]) / 100.0;
170             boolean shrinkHeapInSteps = Boolean.valueOf(args[2]);
171 
172             long maxHeapSize = getMax();
173             int gcTries = (shrinkHeapInSteps ? GC_TRIES : 1);
174 
175             // Initial checks. This also links up everything in these helper methods,
176             // in case it brings more garbage.
177             forceGC(gcTries);
178             verifyRatio(minRatio, maxRatio);
179 
180             // commit 0.5 of total heap size to have enough space
181             // to both shink and expand
182             while (getCommitted() < maxHeapSize / 2) {
183                 garbage.add(new byte[ARRAY_LENGTH]);
184             }
185 
186             forceGC(gcTries);
187             // Verify that current heap free ratio lies between specified limits
188             verifyRatio(minRatio, maxRatio);
189 
190             // Estimate how much memory we have to allocate to force expansion
191             long memoryToFill = (long) (getCommitted() * (1 - minRatio + OVERLOAD))
192                     - getUsed();
193 
194             long previouslyCommitted = getCommitted();
195 
196             while (memoryToFill > 0) {
197                 garbage.add(new byte[CHUNK_SIZE]);
198                 memoryToFill -= CHUNK_SIZE;
199             }
200 
201             forceGC(gcTries);
202             // Verify that after memory allocation heap free ratio is still conforming specified limits
203             verifyRatio(minRatio, maxRatio);
204             // Verify that heap was actually expanded
205             if (previouslyCommitted >= getCommitted()) {
206                 throw new RuntimeException("Heap was not expanded.");
207             }
208 
209             // Estimate how much memory we have to free to force shrinking
210             long memoryToFree = getUsed()
211                     - (long) (getCommitted() * (1 - maxRatio - OVERLOAD));
212 
213             previouslyCommitted = getCommitted();
214 
215             while (memoryToFree > 0 && garbage.size() > 0) {
216                 garbage.remove(garbage.size() - 1);
217                 memoryToFree -= CHUNK_SIZE;
218             }
219 
220             forceGC(gcTries);
221             // Verify that heap free ratio is still conforming specified limits
222             verifyRatio(minRatio, maxRatio);
223             // Verify that heap was actually shrinked
224             if (previouslyCommitted <= getCommitted()) {
225                 throw new RuntimeException("Heap was not shrinked.");
226             }
227         }
228 
forceGC(int gcTries)229         public static void forceGC(int gcTries) {
230             for (int i = 0; i < gcTries; i++) {
231                 System.gc();
232                 try {
233                     Thread.sleep(10);
234                 } catch (InterruptedException ie) {
235                 }
236             }
237         }
238 
239         /**
240          * Verify that heap free ratio is conforming specified limits.
241          * Actual heap free ratio may be very close to one of specified limits,
242          * but exceed for more then VARIANCE.
243          * Verification will also pass if actual ratio is not conforming limits,
244          * but it is not possible to shrink/expand heap.
245          */
verifyRatio(double minRatio, double maxRatio)246         public static void verifyRatio(double minRatio, double maxRatio) {
247             double ratio = getHeapFreeRatio();
248             System.out.println(minRatio + " " + ratio + " " + maxRatio);
249             if (minRatio - ratio > VARIANCE
250                     && getCommitted() < getMax()) {
251                 throw new RuntimeException("Current heap free ratio is lower than "
252                         + "MinHeapFreeRatio (" + ratio + " vs " + minRatio + ").");
253             }
254             if (ratio - maxRatio > VARIANCE
255                     && getUsed() > getInit()) {
256                 throw new RuntimeException("Current heap free ratio is higher than "
257                         + "MaxHeapFreeRatio (" + ratio + " vs " + maxRatio + ").");
258             }
259         }
260 
261         /*
262          * Obtain information about heap size.
263          *
264          * For G1 information summed up for all type of regions,
265          * because tested options affect overall heap sizing.
266          *
267          * For all other GCs return information only for old gen.
268          */
getMax()269         public static long getMax() {
270             return HeapRegionUsageTool.getOldUsage().getMax();
271         }
272 
getInit()273         public static long getInit() {
274             if (GCTypes.OldGCType.getOldGCType() == GCTypes.OldGCType.G1) {
275                 return HeapRegionUsageTool.getEdenUsage().getInit()
276                         + HeapRegionUsageTool.getSurvivorUsage().getInit()
277                         + HeapRegionUsageTool.getOldUsage().getInit();
278             } else {
279                 return HeapRegionUsageTool.getOldUsage().getInit();
280             }
281         }
282 
getUsed()283         public static long getUsed() {
284             if (GCTypes.OldGCType.getOldGCType() == GCTypes.OldGCType.G1) {
285                 return HeapRegionUsageTool.getEdenUsage().getUsed()
286                         + HeapRegionUsageTool.getSurvivorUsage().getUsed()
287                         + HeapRegionUsageTool.getOldUsage().getUsed();
288             } else {
289                 return HeapRegionUsageTool.getOldUsage().getUsed();
290             }
291         }
292 
getCommitted()293         public static long getCommitted() {
294             if (GCTypes.OldGCType.getOldGCType() == GCTypes.OldGCType.G1) {
295                 return HeapRegionUsageTool.getEdenUsage().getCommitted()
296                         + HeapRegionUsageTool.getSurvivorUsage().getCommitted()
297                         + HeapRegionUsageTool.getOldUsage().getCommitted();
298             } else {
299                 return HeapRegionUsageTool.getOldUsage().getCommitted();
300             }
301         }
302 
getFree()303         public static long getFree() {
304             return getCommitted() - getUsed();
305         }
306 
getHeapFreeRatio()307         public static double getHeapFreeRatio() {
308             return getFree() / (double) getCommitted();
309         }
310     }
311 }
312