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 * @test id=default 26 * @modules java.base/java.lang:+open jdk.management 27 * @library /test/lib 28 * @requires vm.continuations & vm.opt.LockingMode != 1 29 * @run junit/othervm -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit 30 */ 31 32 /* 33 * @test id=Xint 34 * @modules java.base/java.lang:+open jdk.management 35 * @library /test/lib 36 * @requires vm.continuations & vm.opt.LockingMode != 1 37 * @run junit/othervm -Xint -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit 38 */ 39 40 /* 41 * @test id=Xcomp 42 * @modules java.base/java.lang:+open jdk.management 43 * @library /test/lib 44 * @requires vm.continuations & vm.opt.LockingMode != 1 45 * @run junit/othervm -Xcomp -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit 46 */ 47 48 /* 49 * @test id=Xcomp-TieredStopAtLevel1 50 * @modules java.base/java.lang:+open jdk.management 51 * @library /test/lib 52 * @requires vm.continuations & vm.opt.LockingMode != 1 53 * @run junit/othervm -Xcomp -XX:TieredStopAtLevel=1 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit 54 */ 55 56 /* 57 * @test id=Xcomp-noTieredCompilation 58 * @modules java.base/java.lang:+open jdk.management 59 * @library /test/lib 60 * @requires vm.continuations & vm.opt.LockingMode != 1 61 * @run junit/othervm -Xcomp -XX:-TieredCompilation -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit 62 */ 63 64 /* 65 * @test id=gc 66 * @modules java.base/java.lang:+open jdk.management 67 * @library /test/lib 68 * @requires vm.debug == true & vm.continuations & vm.opt.LockingMode != 1 69 * @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:+FullGCALot -XX:FullGCALotInterval=1000 -XX:CompileCommand=exclude,KlassInit::lambda$testReleaseAtKlassInit* -XX:CompileCommand=exclude,KlassInit$$Lambda*::run -XX:CompileCommand=exclude,KlassInit$1Driver::foo KlassInit 70 */ 71 72 import java.util.ArrayList; 73 import java.util.List; 74 import java.util.Set; 75 import java.util.concurrent.atomic.AtomicInteger; 76 import java.util.concurrent.atomic.AtomicBoolean; 77 import java.util.concurrent.CountDownLatch; 78 import java.util.concurrent.locks.LockSupport; 79 80 import java.util.stream.Stream; 81 import java.util.stream.Collectors; 82 83 import org.junit.jupiter.api.Test; 84 import static org.junit.jupiter.api.Assertions.*; 85 import org.junit.jupiter.params.ParameterizedTest; 86 import org.junit.jupiter.params.provider.MethodSource; 87 import org.junit.jupiter.params.provider.Arguments; 88 89 class KlassInit { 90 static final int MAX_VTHREAD_COUNT = 8 * Runtime.getRuntime().availableProcessors(); 91 static CountDownLatch finishInvokeStatic1 = new CountDownLatch(1); 92 static CountDownLatch finishInvokeStatic2 = new CountDownLatch(1); 93 static CountDownLatch finishInvokeStatic3 = new CountDownLatch(1); 94 static CountDownLatch finishNew = new CountDownLatch(1); 95 static CountDownLatch finishGetStatic = new CountDownLatch(1); 96 static CountDownLatch finishPutStatic = new CountDownLatch(1); 97 98 /** 99 * Test that threads blocked waiting for klass to be initialized 100 * on invokestatic bytecode release the carrier. 101 */ 102 @Test 103 void testReleaseAtKlassInitInvokeStatic1() throws Exception { 104 class TestClass { 105 static { 106 try { 107 finishInvokeStatic1.await(); 108 } catch(InterruptedException e) {} 109 } 110 static void m() { 111 } 112 } 113 114 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 115 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT]; 116 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 117 final int id = i; 118 started[i] = new CountDownLatch(1); 119 vthreads[i] = Thread.ofVirtual().start(() -> { 120 started[id].countDown(); 121 TestClass.m(); 122 }); 123 } 124 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 125 started[i].await(); 126 await(vthreads[i], Thread.State.WAITING); 127 } 128 129 finishInvokeStatic1.countDown(); 130 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 131 vthreads[i].join(); 132 } 133 } 134 135 /** 136 * Test with static method that takes arguments. 137 */ 138 @Test 139 void testReleaseAtKlassInitInvokeStatic2() throws Exception { 140 class TestClass { 141 static { 142 try { 143 finishInvokeStatic2.await(); 144 } catch(InterruptedException e) {} 145 } 146 static void m(ArrayList<String> list, int id) { 147 String str = list.get(0); 148 if (str != null && str.equals("VThread#" + id)) { 149 list.add("Success"); 150 } 151 } 152 } 153 154 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 155 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT]; 156 ArrayList<String>[] lists = new ArrayList[MAX_VTHREAD_COUNT]; 157 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 158 final int id = i; 159 started[i] = new CountDownLatch(1); 160 lists[i] = new ArrayList<>(List.of("VThread#" + i)); 161 vthreads[i] = Thread.ofVirtual().start(() -> { 162 started[id].countDown(); 163 TestClass.m(lists[id], id); 164 }); 165 } 166 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 167 started[i].await(); 168 await(vthreads[i], Thread.State.WAITING); 169 } 170 171 System.gc(); 172 finishInvokeStatic2.countDown(); 173 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 174 vthreads[i].join(); 175 assertEquals(lists[i].get(1), "Success"); 176 } 177 } 178 179 /** 180 * Test invokestatic as first bytecode in method. 181 */ 182 @Test 183 void testReleaseAtKlassInitInvokeStatic3() throws Exception { 184 class TestClass { 185 static { 186 try { 187 finishInvokeStatic3.await(); 188 } catch(InterruptedException e) {} 189 } 190 static void m() { 191 } 192 } 193 class Driver { 194 static void foo() { 195 TestClass.m(); 196 } 197 } 198 199 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 200 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT]; 201 started[0] = new CountDownLatch(1); 202 vthreads[0] = Thread.ofVirtual().start(() -> { 203 started[0].countDown(); 204 TestClass.m(); 205 }); 206 started[0].await(); 207 await(vthreads[0], Thread.State.WAITING); 208 209 for (int i = 1; i < MAX_VTHREAD_COUNT; i++) { 210 final int id = i; 211 started[i] = new CountDownLatch(1); 212 vthreads[i] = Thread.ofVirtual().start(() -> { 213 started[id].countDown(); 214 Driver.foo(); 215 }); 216 } 217 for (int i = 1; i < MAX_VTHREAD_COUNT; i++) { 218 started[i].await(); 219 await(vthreads[i], Thread.State.WAITING); 220 } 221 222 finishInvokeStatic3.countDown(); 223 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 224 vthreads[i].join(); 225 } 226 } 227 228 /** 229 * Test that threads blocked waiting for klass to be initialized 230 * on new bytecode release the carrier. 231 */ 232 @Test 233 void testReleaseAtKlassInitNew() throws Exception { 234 class TestClass { 235 static { 236 try { 237 finishNew.await(); 238 } catch(InterruptedException e) {} 239 } 240 void m() { 241 } 242 } 243 244 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 245 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT]; 246 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 247 final int id = i; 248 started[i] = new CountDownLatch(1); 249 vthreads[i] = Thread.ofVirtual().start(() -> { 250 started[id].countDown(); 251 TestClass x = new TestClass(); 252 x.m(); 253 }); 254 } 255 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 256 started[i].await(); 257 await(vthreads[i], Thread.State.WAITING); 258 } 259 260 finishNew.countDown(); 261 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 262 vthreads[i].join(); 263 } 264 } 265 266 /** 267 * Test that threads blocked waiting for klass to be initialized 268 * on getstatic bytecode release the carrier. 269 */ 270 @Test 271 void testReleaseAtKlassInitGetStatic() throws Exception { 272 class TestClass { 273 static { 274 try { 275 finishGetStatic.await(); 276 } catch(InterruptedException e) {} 277 } 278 public static int NUMBER = 150; 279 } 280 281 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 282 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT]; 283 AtomicInteger[] result = new AtomicInteger[MAX_VTHREAD_COUNT]; 284 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 285 final int id = i; 286 started[i] = new CountDownLatch(1); 287 result[i] = new AtomicInteger(); 288 vthreads[i] = Thread.ofVirtual().start(() -> { 289 started[id].countDown(); 290 result[id].set(TestClass.NUMBER); 291 }); 292 } 293 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 294 started[i].await(); 295 await(vthreads[i], Thread.State.WAITING); 296 } 297 298 finishGetStatic.countDown(); 299 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 300 vthreads[i].join(); 301 assertEquals(result[i].get(), TestClass.NUMBER); 302 } 303 } 304 305 /** 306 * Test that threads blocked waiting for klass to be initialized 307 * on putstatic release the carrier. 308 */ 309 @Test 310 void testReleaseAtKlassInitPutStatic() throws Exception { 311 class TestClass { 312 static { 313 try { 314 finishPutStatic.await(); 315 } catch(InterruptedException e) {} 316 } 317 public static int NUMBER; 318 } 319 320 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT]; 321 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT]; 322 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 323 final int id = i; 324 started[i] = new CountDownLatch(1); 325 vthreads[i] = Thread.ofVirtual().start(() -> { 326 started[id].countDown(); 327 TestClass.NUMBER = id; 328 }); 329 } 330 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 331 started[i].await(); 332 await(vthreads[i], Thread.State.WAITING); 333 } 334 335 finishPutStatic.countDown(); 336 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) { 337 vthreads[i].join(); 338 } 339 } 340 341 /** 342 * Test that interruptions during preemption on klass init 343 * are preserved. 344 */ 345 @ParameterizedTest 346 @MethodSource("interruptTestCases") 347 void testReleaseAtKlassInitPreserverInterrupt(int timeout, Runnable m, CountDownLatch finish) throws Exception { 348 // Start vthread1 and wait until it blocks in TestClassX initializer 349 var vthread1_started = new CountDownLatch(1); 350 var vthread1 = Thread.ofVirtual().start(() -> { 351 vthread1_started.countDown(); 352 m.run(); 353 }); 354 vthread1_started.await(); 355 await(vthread1, Thread.State.WAITING); 356 357 // Start vthread2 and wait until it gets preempted on TestClassX initialization 358 var lock = new Object(); 359 var interruptedException = new AtomicBoolean(); 360 var vthread2_started = new CountDownLatch(1); 361 var vthread2 = Thread.ofVirtual().start(() -> { 362 vthread2_started.countDown(); 363 m.run(); 364 synchronized (lock) { 365 try { 366 if (timeout > 0) { 367 lock.wait(timeout); 368 } else { 369 lock.wait(); 370 } 371 } catch (InterruptedException e) { 372 // check stack trace has the expected frames 373 Set<String> expected = Set.of("wait0", "wait", "run"); 374 Set<String> methods = Stream.of(e.getStackTrace()) 375 .map(StackTraceElement::getMethodName) 376 .collect(Collectors.toSet()); 377 assertTrue(methods.containsAll(expected)); 378 interruptedException.set(true); 379 } 380 } 381 }); 382 vthread2_started.await(); 383 await(vthread2, Thread.State.WAITING); 384 385 // Interrupt vthread2 and let initialization of TestClassX finish 386 vthread2.interrupt(); 387 finish.countDown(); 388 vthread1.join(); 389 vthread2.join(); 390 assertTrue(interruptedException.get()); 391 } 392 393 static CountDownLatch finishInterrupt0 = new CountDownLatch(1); 394 class TestClass0 { 395 static { 396 try { 397 finishInterrupt0.await(); 398 } catch(InterruptedException e) {} 399 } 400 static void m() {} 401 } 402 403 static CountDownLatch finishInterrupt30000 = new CountDownLatch(1); 404 class TestClass30000 { 405 static { 406 try { 407 finishInterrupt30000.await(); 408 } catch(InterruptedException e) {} 409 } 410 static void m() {} 411 } 412 413 static CountDownLatch finishInterruptMax = new CountDownLatch(1); 414 class TestClassMax { 415 static { 416 try { 417 finishInterruptMax.await(); 418 } catch(InterruptedException e) {} 419 } 420 static void m() {} 421 } 422 423 static Stream<Arguments> interruptTestCases() { 424 return Stream.of( 425 Arguments.of(0, (Runnable) TestClass0::m, finishInterrupt0), 426 Arguments.of(30000, (Runnable) TestClass30000::m, finishInterrupt30000), 427 Arguments.of(Integer.MAX_VALUE, (Runnable) TestClassMax::m, finishInterruptMax) 428 ); 429 } 430 431 /** 432 * Waits for the given thread to reach a given state. 433 */ 434 private void await(Thread thread, Thread.State expectedState) throws InterruptedException { 435 Thread.State state = thread.getState(); 436 while (state != expectedState) { 437 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 438 Thread.sleep(10); 439 state = thread.getState(); 440 } 441 } 442 }