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 * @library /test/lib
27 * @requires vm.continuations
28 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
29 * @run junit/othervm/timeout=480 -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 * @library /test/lib
35 * @requires vm.continuations
36 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
37 * @run junit/othervm/timeout=480 -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 * @library /test/lib
43 * @requires vm.continuations
44 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
45 * @run junit/othervm/timeout=480 -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 * @library /test/lib
51 * @requires vm.continuations
52 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
53 * @run junit/othervm/timeout=480 -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 * @library /test/lib
59 * @requires vm.continuations
60 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
61 * @run junit/othervm/timeout=480 -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 * @library /test/lib
67 * @requires vm.debug == true & vm.continuations
68 * @requires os.arch=="amd64" | os.arch=="x86_64" | os.arch=="aarch64" | os.arch=="riscv64"
69 * @run junit/othervm/timeout=480 -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.stream.Stream;
79 import java.util.stream.Collectors;
80
81 import org.junit.jupiter.api.Test;
82 import org.junit.jupiter.params.ParameterizedTest;
83 import org.junit.jupiter.params.provider.MethodSource;
84 import org.junit.jupiter.params.provider.Arguments;
85 import static org.junit.jupiter.api.Assertions.*;
86
87 class KlassInit {
88 private static final int MAX_VTHREAD_COUNT = 8 * Runtime.getRuntime().availableProcessors();
89
90 private static final CountDownLatch finishInvokeStatic1 = new CountDownLatch(1);
91 private static final CountDownLatch finishInvokeStatic2 = new CountDownLatch(1);
92 private static final CountDownLatch finishInvokeStatic3 = new CountDownLatch(1);
93 private static final CountDownLatch finishNew = new CountDownLatch(1);
94 private static final CountDownLatch finishGetStatic = new CountDownLatch(1);
95 private static final CountDownLatch finishPutStatic = new CountDownLatch(1);
96 private static final CountDownLatch finishFailedInit = 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 * Test case of threads blocked waiting for klass to be initialized
433 * when the klass initialization fails.
434 */
435 @Test
436 void testReleaseAtKlassInitFailedInit() throws Exception {
437 class TestClass {
438 static int[] a = {1, 2, 3};
439 static {
440 try {
441 finishFailedInit.await();
442 a[3] = 4;
443 } catch (InterruptedException e) {}
444 }
445 static void m() {
446 }
447 }
448
449 Thread[] vthreads = new Thread[MAX_VTHREAD_COUNT];
450 CountDownLatch[] started = new CountDownLatch[MAX_VTHREAD_COUNT];
451 AtomicInteger failedCount = new AtomicInteger();
452 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
453 final int id = i;
454 started[i] = new CountDownLatch(1);
455 vthreads[i] = Thread.ofVirtual().start(() -> {
456 started[id].countDown();
457 try {
458 TestClass.m();
459 } catch (NoClassDefFoundError e) {
460 failedCount.getAndIncrement();
461 } catch (ExceptionInInitializerError e) {
462 failedCount.getAndIncrement();
463 }
464 });
465 }
466 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
467 started[i].await();
468 await(vthreads[i], Thread.State.WAITING);
469 }
470
471 finishFailedInit.countDown();
472 for (int i = 0; i < MAX_VTHREAD_COUNT; i++) {
473 vthreads[i].join();
474 }
475 assertEquals(MAX_VTHREAD_COUNT, failedCount.get());
476 }
477
478 /**
479 * Waits for the given thread to reach a given state.
480 */
481 private void await(Thread thread, Thread.State expectedState) throws InterruptedException {
482 Thread.State state = thread.getState();
483 while (state != expectedState) {
484 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated");
485 Thread.sleep(10);
486 state = thread.getState();
487 }
488 }
489 }