1 /*
  2  * Copyright (c) 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 
 25 /*
 26  * @test various test cases for archived WeakReference objects.
 27  * @requires vm.cds.write.archived.java.heap
 28  * @requires vm.cds.supports.aot.class.linking
 29  * @requires vm.debug
 30  * @comment work around JDK-8345635
 31  * @requires !vm.jvmci.enabled
 32  * @comment TODO ...tested only against G1
 33  * @requires vm.gc.G1
 34  * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds
 35  * @build WeakReferenceTest
 36  * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar weakref.jar
 37  *             WeakReferenceTestApp WeakReferenceTestApp$Inner ShouldNotBeAOTInited ShouldNotBeArchived SharedQueue
 38  * @run driver WeakReferenceTest AOT
 39  */
 40 
 41 import java.lang.ref.WeakReference;
 42 import java.lang.ref.ReferenceQueue;
 43 import jdk.test.lib.cds.CDSAppTester;
 44 import jdk.test.lib.process.OutputAnalyzer;
 45 import jdk.test.lib.helpers.ClassFileInstaller;
 46 
 47 public class WeakReferenceTest {
 48     static final String appJar = ClassFileInstaller.getJarPath("weakref.jar");
 49     static final String mainClass = "WeakReferenceTestApp";
 50 
 51     public static void main(String[] args) throws Exception {
 52         Tester t = new Tester();
 53         t.run(args);
 54     }
 55 
 56     static class Tester extends CDSAppTester {
 57         public Tester() {
 58             super(mainClass);
 59         }
 60 
 61         @Override
 62         public String classpath(RunMode runMode) {
 63             return appJar;
 64         }
 65 
 66         @Override
 67         public String[] vmArgs(RunMode runMode) {
 68             if (runMode == RunMode.ASSEMBLY) {
 69                 return new String[] {
 70                     "-Xlog:gc,cds+class=debug",
 71                     "-XX:AOTInitTestClass=WeakReferenceTestApp",
 72                     "-Xlog:cds+map,cds+map+oops=trace:file=cds.oops.txt:none:filesize=0",
 73                 };
 74             } else {
 75                 return new String[] {
 76                     "-Xlog:gc",
 77                 };
 78             }
 79         }
 80 
 81         @Override
 82         public String[] appCommandLine(RunMode runMode) {
 83             return new String[] {
 84                 mainClass,
 85                 runMode.toString(),
 86             };
 87         }
 88 
 89         @Override
 90         public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {
 91             out.shouldHaveExitValue(0);
 92             out.shouldNotContain("Unexpected exception:");
 93         }
 94     }
 95 }
 96 
 97 // TODO: Add test: cleaner should work in both assembly phase and production run
 98 
 99 class WeakReferenceTestApp {
100     // This class is NOT aot-initialized
101     static class Inner {
102         static boolean WeakReferenceTestApp_clinit_executed;
103     }
104 
105     static {
106         Inner.WeakReferenceTestApp_clinit_executed = true;
107 
108         // During the assembly phase, this block of code is called during the assembly
109         // phase (triggered by the -XX:AOTInitTestClass=WeakReferenceTestApp flag).
110         // It runs the clinit_for_testXXX() method to set up the aot-initialized data structures
111         // that are used by  each testXXX() function.
112         //
113         // Note that this function is also called during the training run.
114         // This function is NOT called during the production run, because WeakReferenceTestApp
115         // is aot-initialized.
116 
117         clinit_for_testCollectedInAssembly();
118         clinit_for_testWeakReferenceCollection();
119         clinit_for_testQueue();
120     }
121 
122     static WeakReference makeRef() {
123         System.out.println("WeakReferenceTestApp::makeRef() is executed");
124         WeakReference r = new WeakReference(root);
125         System.out.println("r.get() = " + r.get());
126 
127         ShouldNotBeAOTInited.doit();
128         return r;
129     }
130 
131     static WeakReference makeRef2() {
132         return new WeakReference(new WeakReferenceTestApp());
133     }
134 
135     public static void main(String[] args) {
136         try {
137             runTests(args);
138         } catch (Throwable t) {
139             System.err.println("Unexpected exception:");
140             t.printStackTrace();
141             System.exit(1);
142         }
143     }
144 
145     static void runTests(String[] args) throws Exception {
146         boolean isProduction = args[0].equals("PRODUCTION");
147 
148         if (isProduction && Inner.WeakReferenceTestApp_clinit_executed) {
149             throw new RuntimeException("WeakReferenceTestApp should have been aot-inited");
150         }
151 
152         if (isProduction) {
153             // A GC should have happened before the heap objects are written into
154             // the AOT cache. So any unreachable referents should have been collected.
155         } else {
156             // We are in the training run. Simulate the GC mentioned in the above comment,
157             // so the test cases should observe the same states as in the production run.
158             System.gc();
159         }
160 
161         testCollectedInAssembly(isProduction);
162         testWeakReferenceCollection(isProduction);
163         testQueue(isProduction);
164     }
165 
166     //----------------------------------------------------------------------
167     // Set up for testCollectedInAssembly()
168     static WeakReference refToCollectedObj;
169 
170     static void clinit_for_testCollectedInAssembly() {
171         // The referent will be GC-ed in the assembly run when the JVM forces a full GC.
172         refToCollectedObj = new WeakReference(new String("collected in assembly"));
173     }
174 
175     // [TEST CASE] Test the storage of a WeakReference whose referent has been collected during the assembly phase.
176     static void testCollectedInAssembly(boolean isProduction) {
177         System.out.println("refToCollectedObj.get() = " + refToCollectedObj.get());
178         System.out.println("refToCollectedObj.isEnqueued() = " + refToCollectedObj.isEnqueued());
179 
180         if (refToCollectedObj.get() != null) {
181             throw new RuntimeException("refToCollectedObj.get() should have been GC'ed");
182         }
183 
184         /*
185          * FIXME -- why does this fail, even in training run?
186 
187         if (!refToCollectedObj.isEnqueued()) {
188             throw new RuntimeException("refToCollectedObj.isEnqueued() should be true");
189         }
190         */
191     }
192 
193     //----------------------------------------------------------------------
194     // Set up for testWeakReferenceCollection()
195     static Object root;
196     static WeakReference ref;
197 
198     static void clinit_for_testWeakReferenceCollection() {
199         root = new WeakReferenceTestApp();
200         ref = makeRef();
201     }
202 
203     // [TEST CASE] A WeakReference allocated in assembly phase should be collectable in the production run
204     static void testWeakReferenceCollection(boolean isProduction) {
205         WeakReference ref2 = makeRef2();
206         System.out.println("ref.get() = " + ref.get());   // created during assembly phase
207         System.out.println("ref2.get() = " + ref2.get()); // created during production run
208 
209         if (ref.get() == null) {
210             throw new RuntimeException("ref.get() should not be null");
211         }
212         if (ref2.get() == null) {
213             throw new RuntimeException("ref2.get() should not be null");
214         }
215 
216         System.out.println("... running GC ...");
217         root = null;
218         System.gc();
219 
220         System.out.println("ref.get() = " + ref.get());
221         System.out.println("ref2.get() = " + ref2.get());
222 
223         if (ref.get() != null) {
224             throw new RuntimeException("ref.get() should be null");
225         }
226         if (ref2.get() != null) {
227             throw new RuntimeException("ref2.get() should be null");
228         }
229 
230         System.out.println("ShouldNotBeAOTInited.doit_executed = " + ShouldNotBeAOTInited.doit_executed);
231         if (isProduction && ShouldNotBeAOTInited.doit_executed) {
232             throw new RuntimeException("ShouldNotBeAOTInited should not have been aot-inited");
233         }
234     }
235 
236     //----------------------------------------------------------------------
237     // Set up for testQueue()
238     static WeakReference refWithQueue;
239     static SharedQueue sharedQueueInstance;
240 
241     static void clinit_for_testQueue() {
242         // Make sure SharedQueue is also cached in *initialized* state.
243         sharedQueueInstance = SharedQueue.sharedQueueInstance;
244 
245         refWithQueue = new WeakReference(String.class, SharedQueue.queue());
246         ShouldNotBeArchived.ref = new WeakReference(ShouldNotBeArchived.instance, SharedQueue.queue());
247 
248 
249         // Set to 2 in training run and assembly phase, but this state shouldn't be stored in
250         // AOT cache.
251         ShouldNotBeArchived.state = 2;
252     }
253 
254     // [TEST CASE] Unrelated WeakReferences shouldn't be cached even if they are registered with the same queue
255     static void testQueue(boolean isProduction) {
256         System.out.println("refWithQueue.get() = " + refWithQueue.get());
257         System.out.println("ShouldNotBeArchived.state = " + ShouldNotBeArchived.state);
258 
259         // [1] Although refWithQueue and ShouldNotBeArchived.ref are registered with the same queue, as both
260         //     of their referents are strongly referenced, they are not added to the queue's "head".
261         //     (Per javadoc: "registered reference objects are appended by the garbage collector after the
262         //     appropriate reachability changes are detected");
263         // [2] When the assembly phase scans refWithQueue, it shouldn't discover ShouldNotBeArchived.ref (via the queue),
264         //     so ShouldNotBeArchived.ref should not be stored in the AOT cache.
265         // [3] As a result, ShouldNotBeArchived should be cached in the *not initialized" state. Its <clinit>
266         //     will be executed in the production run to set ShouldNotBeArchived.state to 1.
267         if (isProduction && ShouldNotBeArchived.state != 1) {
268             throw new RuntimeException("ShouldNotBeArchived should be 1 but is " + ShouldNotBeArchived.state);
269         }
270     }
271 }
272 
273 class ShouldNotBeAOTInited {
274     static WeakReference ref;
275     static boolean doit_executed;
276     static {
277         System.out.println("ShouldNotBeAOTInited.<clinit> called");
278     }
279     static void doit() {
280         System.out.println("ShouldNotBeAOTInited.doit()> called");
281         doit_executed = true;
282         ref = new WeakReference(new ShouldNotBeAOTInited());
283     }
284 }
285 
286 class ShouldNotBeArchived {
287     static ShouldNotBeArchived instance = new ShouldNotBeArchived();
288     static WeakReference ref;
289     static int state = 1;
290 }
291 
292 class SharedQueue {
293     static SharedQueue sharedQueueInstance = new SharedQueue();
294     private ReferenceQueue<Object> theQueue = new ReferenceQueue<Object>();
295 
296     static ReferenceQueue<Object> queue() {
297         return sharedQueueInstance.theQueue;
298     }
299 }