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 }