1 /*
2 * Copyright (c) 2014, 2025, 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.g1;
25
26 /*
27 * @test TestEagerReclaimHumongousRegions
28 * @bug 8051973
29 * @summary Test to make sure that eager reclaim of humongous objects correctly works.
30 * @requires vm.gc.G1
31 * @requires vm.debug
32 * @library /test/lib /testlibrary /
33 * @modules java.base/jdk.internal.misc
34 * java.management
35 * @build jdk.test.whitebox.WhiteBox
36 * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
37 * @run main/othervm -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI gc.g1.TestEagerReclaimHumongousRegions
38 */
39
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.regex.Matcher;
43 import java.util.regex.MatchResult;
44 import java.util.regex.Pattern;
45
46 import jdk.test.lib.Asserts;
47 import jdk.test.lib.process.OutputAnalyzer;
48 import jdk.test.lib.process.ProcessTools;
49 import jdk.test.whitebox.WhiteBox;
50
51 public class TestEagerReclaimHumongousRegions {
52 private static final WhiteBox WB = WhiteBox.getWhiteBox();
53
54 enum ObjectType { TYPE_ARRAY, OBJ_ARRAY }
55 enum ReferencePolicy { KEEP, DROP }
56 enum AllocationTiming { BEFORE_MARK_START, AFTER_MARK_START}
57
58 enum ExpectedState {
59 MARKED_CANDIDATE_RECLAIMED(true, true, true),
60 MARKED_CANDIDATE_NOT_RECLAIMED(true, true, false),
61 MARKED_NOTCANDIDATE_NOTRECLAIMED(true, false, false),
62 NOTMARKED_CANDIDATE_RECLAIMED(false, true, true),
63 NOTMARKED_CANDIDATE_NOTRECLAIMED(false, true, false),
64 NOTMARKED_NOTCANDIDATE_NOTRECLAIMED(false, false, false);
65
66 final boolean marked;
67 final boolean candidate;
68 final boolean reclaimed;
69
70 ExpectedState(boolean marked, boolean candidate, boolean reclaimed) {
71 this.marked = marked;
72 this.candidate = candidate;
73 this.reclaimed = reclaimed;
74 }
75 }
76
77 /**
78 * Run the helper VM, passing configuration arguments, simulating an application allocating some kind of humongous object at a
79 * point during the induced concurrent mark, and executing a young gc.
80 *
81 * @param type Whether the allocated humongous object should be a typeArray, or an objArray.
82 * @param refPolicy Drop the reference to the allocated object after reaching the given phase or keep.
83 * @param timing Allocate the humongous objects before or after reaching the given phase.
84 * @param phase The phase during concurrent mark to reach before triggering a young garbage collection.
85 * @return Returns the stdout of the VM.
86 */
87 private static String runHelperVM(List<String> args, ObjectType type, ReferencePolicy refPolicy, AllocationTiming timing, String phase) throws Exception {
88
89 boolean useTypeArray = (type == ObjectType.TYPE_ARRAY);
90 boolean keepReference = (refPolicy == ReferencePolicy.KEEP);
91 boolean allocateAfter = (timing == AllocationTiming.AFTER_MARK_START);
92
93 args.add(TestEagerReclaimHumongousRegionsClearMarkBitsRunner.class.getName());
94 args.add(String.valueOf(useTypeArray));
95 args.add(String.valueOf(keepReference));
96 args.add(String.valueOf(allocateAfter));
97 args.add(phase);
98
99 OutputAnalyzer output = ProcessTools.executeLimitedTestJava(args);
100
101 String log = output.getStdout();
102 System.out.println(log);
103 output.shouldHaveExitValue(0);
104 return log;
105 }
106
107 private static List<String> testArgs() throws Exception {
108 return List.of("-XX:+UseG1GC",
109 "-Xmx20M",
110 "-Xms20m",
111 "-XX:+UnlockDiagnosticVMOptions",
112 "-XX:+VerifyAfterGC",
113 "-Xbootclasspath/a:.",
114 "-Xlog:gc=debug,gc+humongous=debug",
115 "-XX:+UnlockDiagnosticVMOptions",
116 "-XX:+WhiteBoxAPI");
117 }
118
119 private static String boolToInt(boolean value) {
120 return value ? "1" : "0";
121 }
122
123 private static void verifyLog(String log, AllocationTiming timing, ExpectedState expected) {
124 // Find the log output indicating that the humongous object has been reclaimed, and marked and verify for the expected results.
125 // [0.351s][debug][gc,humongous] GC(3) Humongous region 2 (object size 4194320 @ 0x00000000fee00000) remset 0 code roots 0 marked 1 pinned count 0 reclaim candidate 1 type array 1
126
127 // Now check the result of the reclaim attempt. We are interested in the last such message (as mentioned above, we might get two).
128 String patternString = "gc,humongous.* marked (\\d) pin.*candidate (\\d)";
129 Pattern pattern = Pattern.compile(patternString);
130 Matcher matcher = pattern.matcher(log);
131
132 List<MatchResult> found = new ArrayList<MatchResult>();
133 while (matcher.find()) {
134 found.add(matcher.toMatchResult());
135 }
136
137 Asserts.assertTrue(found.size() == 1 || found.size() == 2, "Unexpected number of log messages " + found.size());
138
139 if (found.size() == 2) {
140 Asserts.assertTrue(timing == AllocationTiming.BEFORE_MARK_START, "Should only have two messages if allocating the object before mark start");
141 MatchResult mr = found.removeFirst();
142 Asserts.assertTrue(mr.group(1).equals(boolToInt(false)), "Should not be marked before mark start " + mr.group());
143 Asserts.assertTrue(mr.group(2).equals(boolToInt(true)), "Should be candidate before mark start " + mr.group());
144 }
145
146 MatchResult mr = found.removeFirst();
147 Asserts.assertTrue(mr.group(1).equals(boolToInt(expected.marked)), "Expected that region was " + (expected.marked ? "" : "not ") + " marked but is " + mr.group());
148 Asserts.assertTrue(mr.group(2).equals(boolToInt(expected.candidate)), "Expected that region was " + (expected.candidate ? "" : "not ") + " candidate but is " + mr.group());
149
150 boolean reclaimed = Pattern.compile("Reclaimed humongous region .*").matcher(log).find();
151 Asserts.assertTrue(expected.reclaimed == reclaimed, "Wrong log output reclaiming humongous region");
152 }
153
154 private static void runTest(ObjectType type,
155 ReferencePolicy refPolicy,
156 AllocationTiming timing,
157 String phase,
158 ExpectedState expected) throws Exception {
159 List<String> vmArgs = testArgs();
160
161 ArrayList<String> args = new ArrayList(vmArgs);
162 String log = runHelperVM(args, type, refPolicy, timing, phase);
163 verifyLog(log, timing, expected);
164
165 ArrayList<String> jfrArgs = new ArrayList(vmArgs);
166 jfrArgs.addLast("-XX:StartFlightRecording=settings=profile");
167 String jfrLog = runHelperVM(jfrArgs, type, refPolicy, timing, phase);
168 verifyLog(jfrLog, timing, expected);
169 }
170
171 public static void main(String[] args) throws Exception {
172 System.out.println("Tests checking eager reclaim for when the object is allocated before mark start.");
173 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED);
174 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED);
175 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
176
177 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED);
178 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED);
179 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
180
181 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_NOTCANDIDATE_NOTRECLAIMED);
182 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_RECLAIMED);
183 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
184
185 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.MARKED_NOTCANDIDATE_NOTRECLAIMED);
186 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.MARKED_CANDIDATE_NOT_RECLAIMED);
187 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.BEFORE_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
188
189 System.out.println("Tests checking eager reclaim for when the object is allocated after mark start.");
190 // These must not be marked (as they were allocated after mark start), and they are always candidates. Reclamation depends on whether there is a reference.
191 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
192 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
193 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
194
195 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
196 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
197 runTest(ObjectType.TYPE_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
198
199 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
200 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
201 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.DROP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_RECLAIMED);
202
203 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.BEFORE_MARKING_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
204 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_REBUILD_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
205 runTest(ObjectType.OBJ_ARRAY, ReferencePolicy.KEEP, AllocationTiming.AFTER_MARK_START, WB.G1_BEFORE_CLEANUP_COMPLETED, ExpectedState.NOTMARKED_CANDIDATE_NOTRECLAIMED);
206 }
207 }
208
209 class TestEagerReclaimHumongousRegionsClearMarkBitsRunner {
210 private static final WhiteBox WB = WhiteBox.getWhiteBox();
211 private static final int SIZE = 1024 * 1024;
212
213 private static Object allocateHumongousObj(boolean useTypeArray) {
214 if (useTypeArray) {
215 return new int[SIZE];
216 } else {
217 return new Object[SIZE];
218 }
219 }
220
221 public static void main(String[] args) throws Exception {
222 if (args.length != 4) {
223 throw new Exception("Invalid number of arguments " + args.length);
224 }
225 boolean useTypeArray = Boolean.parseBoolean(args[0]);
226 boolean keepReference = Boolean.parseBoolean(args[1]);
227 boolean allocateAfter = Boolean.parseBoolean(args[2]);
228 String phase = args[3];
229
230 System.out.println("useTypeArray: " + useTypeArray + " keepReference: " + keepReference + " allocateAfter " + allocateAfter + " phase: " + phase);
231 WB.fullGC();
232
233 Object largeObj = null; // Allocated humongous object.
234 if (!allocateAfter) {
235 largeObj = allocateHumongousObj(useTypeArray);
236 }
237
238 WB.concurrentGCAcquireControl();
239 WB.concurrentGCRunTo(phase);
240
241 System.out.println("Phase " + phase + " reached");
242
243 if (allocateAfter) {
244 largeObj = allocateHumongousObj(useTypeArray);
245 }
246
247 if (!keepReference) {
248 largeObj = null;
249 }
250 WB.youngGC(); // May reclaim the humongous object.
251
252 WB.concurrentGCRunToIdle();
253
254 System.out.println("Large object at " + largeObj); // Keepalive.
255 }
256 }