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 }