/* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ /* * @test various test cases for archived WeakReference objects. * @requires vm.cds.write.archived.java.heap * @requires vm.cds.supports.aot.class.linking * @requires vm.debug * @library /test/jdk/lib/testlibrary /test/lib /test/hotspot/jtreg/runtime/cds/appcds * @build WeakReferenceTest * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar weakref.jar * WeakReferenceTestApp WeakReferenceTestApp$Inner ShouldNotBeAOTInited ShouldNotBeArchived SharedQueue * WeakReferenceTestBadApp1 WeakReferenceTestBadApp2 * @run driver WeakReferenceTest AOT --two-step-training */ import java.lang.ref.WeakReference; import java.lang.ref.ReferenceQueue; import jdk.test.lib.cds.CDSAppTester; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.helpers.ClassFileInstaller; import jtreg.SkippedException; public class WeakReferenceTest { static final String appJar = ClassFileInstaller.getJarPath("weakref.jar"); static final String goodApp = "WeakReferenceTestApp"; static final String badApp1 = "WeakReferenceTestBadApp1"; static final String badApp2 = "WeakReferenceTestBadApp2"; public static void main(String[] args) throws Exception { new Tester(goodApp).run(args); runBadApp(badApp1, args); runBadApp(badApp2, args); } static void runBadApp(String badApp, String[] args) throws Exception { try { new Tester(badApp).run(args); throw new RuntimeException(badApp + " did not fail in assembly phase as expected"); } catch (SkippedException e) { System.out.println("Negative test: expected SkippedException"); } } static class Tester extends CDSAppTester { String mainClass; public Tester(String mainClass) { super(mainClass); this.mainClass = mainClass; if (mainClass != goodApp) { setCheckExitValue(false); } } @Override public String classpath(RunMode runMode) { return appJar; } @Override public String[] vmArgs(RunMode runMode) { if (runMode == RunMode.ASSEMBLY) { return new String[] { "-Xlog:gc,cds+class=debug", "-XX:AOTInitTestClass=" + mainClass, }; } else { return new String[] { "-Xlog:gc", }; } } @Override public String[] appCommandLine(RunMode runMode) { return new String[] { mainClass, runMode.toString(), }; } @Override public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { if (runMode == RunMode.ASSEMBLY && mainClass != goodApp) { out.shouldNotHaveExitValue(0); out.shouldMatch("Cannot archive reference object .* of class java.lang.ref.WeakReference"); if (mainClass == badApp1) { out.shouldContain("referent cannot be null"); } else { out.shouldContain("referent is not registered with CDS.keepAlive()"); } throw new SkippedException("Assembly phase expected to fail"); } out.shouldHaveExitValue(0); out.shouldNotContain("Unexpected exception:"); } } } class WeakReferenceTestApp { static class Inner { // This class is NOT aot-initialized static boolean WeakReferenceTestApp_clinit_executed; } static { Inner.WeakReferenceTestApp_clinit_executed = true; // This static {} block is executed the training run (which uses no AOT cache). // // During the assembly phase, this static {} block of is also executed // (triggered by the -XX:AOTInitTestClass=WeakReferenceTestApp flag). // It runs the aot_init_for_testXXX() method to set up the aot-initialized data structures // that are used by each testXXX() function. // // This block is NOT executed during the production run, because WeakReferenceTestApp // is aot-initialized. aot_init_for_testCollectedInAssembly(); aot_init_for_testWeakReferenceCollection(); } public static void main(String[] args) { try { runTests(args); } catch (Throwable t) { System.err.println("Unexpected exception:"); t.printStackTrace(); System.exit(1); } } static void runTests(String[] args) throws Exception { boolean isProduction = args[0].equals("PRODUCTION"); if (isProduction && Inner.WeakReferenceTestApp_clinit_executed) { throw new RuntimeException("WeakReferenceTestApp should have been aot-inited"); } if (isProduction) { // A GC should have happened before the heap objects are written into // the AOT cache. So any unreachable referents should have been collected. } else { // We are in the training run. Simulate the GC mentioned in the above comment, // so the test cases should observe the same states as in the production run. System.gc(); } testCollectedInAssembly(isProduction); testWeakReferenceCollection(isProduction); } //---------------------------------------------------------------------- // Set up for testCollectedInAssembly() static WeakReference refToCollectedObj; static void aot_init_for_testCollectedInAssembly() { // The referent will be GC-ed in the assembly run when the JVM forces a full GC. refToCollectedObj = new WeakReference(new String("collected in assembly")); } // [TEST CASE] Test the storage of a WeakReference whose referent has been collected during the assembly phase. static void testCollectedInAssembly(boolean isProduction) { System.out.println("refToCollectedObj.get() = " + refToCollectedObj.get()); if (refToCollectedObj.get() != null) { throw new RuntimeException("refToCollectedObj.get() should have been GC'ed"); } } //---------------------------------------------------------------------- // Set up for testWeakReferenceCollection() static Object root; static WeakReference ref; static void aot_init_for_testWeakReferenceCollection() { root = new String("to be collected in production"); ref = makeRef(); } static WeakReference makeRef() { System.out.println("WeakReferenceTestApp::makeRef() is executed"); WeakReference r = new WeakReference(root); System.out.println("r.get() = " + r.get()); ShouldNotBeAOTInited.doit(); return r; } static WeakReference makeRef2() { return new WeakReference(new String("to be collected in production")); } // [TEST CASE] A WeakReference allocated in assembly phase should be collectable in the production run static void testWeakReferenceCollection(boolean isProduction) { WeakReference ref2 = makeRef2(); System.out.println("ref.get() = " + ref.get()); // created during assembly phase System.out.println("ref2.get() = " + ref2.get()); // created during production run if (ref.get() == null) { throw new RuntimeException("ref.get() should not be null"); } System.out.println("... running GC ..."); root = null; // make ref.referent() eligible for collection System.gc(); System.out.println("ref.get() = " + ref.get()); System.out.println("ref2.get() = " + ref2.get()); if (ref.get() != null) { throw new RuntimeException("ref.get() should be null"); } if (ref2.get() != null) { throw new RuntimeException("ref2.get() should be null"); } System.out.println("ShouldNotBeAOTInited.doit_executed = " + ShouldNotBeAOTInited.doit_executed); if (isProduction && ShouldNotBeAOTInited.doit_executed) { throw new RuntimeException("ShouldNotBeAOTInited should not have been aot-inited"); } } } class ShouldNotBeAOTInited { static WeakReference ref; static boolean doit_executed; static { System.out.println("ShouldNotBeAOTInited. called"); } static void doit() { System.out.println("ShouldNotBeAOTInited.doit()> called"); doit_executed = true; ref = new WeakReference(new ShouldNotBeAOTInited()); } } class ShouldNotBeArchived { static ShouldNotBeArchived instance = new ShouldNotBeArchived(); static WeakReference ref; static int state = 1; } class SharedQueue { static SharedQueue sharedQueueInstance = new SharedQueue(); private ReferenceQueue theQueue = new ReferenceQueue(); static ReferenceQueue queue() { return sharedQueueInstance.theQueue; } } class WeakReferenceTestBadApp1 { static WeakReference refWithQueue; static SharedQueue sharedQueueInstance; static { // See comments in aotReferenceObjSupport.cpp: group [2] references cannot have null referent. sharedQueueInstance = SharedQueue.sharedQueueInstance; refWithQueue = new WeakReference(String.class, SharedQueue.queue()); refWithQueue.clear(); } public static void main(String args[]) {} } class WeakReferenceTestBadApp2 { static WeakReference refWithQueue; static SharedQueue sharedQueueInstance; static { // See comments in aotReferenceObjSupport.cpp: group [2] references must be registered with CDS.keepAlive() sharedQueueInstance = SharedQueue.sharedQueueInstance; refWithQueue = new WeakReference(String.class, SharedQueue.queue()); } public static void main(String args[]) {} }