From db340e54f83cf7bf72abb94c9cf9cdac007ed38a Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Fri, 30 May 2025 21:11:58 +0000 Subject: [PATCH] 8356222: Thread.print command reports waiting on the Class initialization monitor for both carrier and virtual threads Reviewed-by: alanb, sspitsyn --- src/hotspot/share/runtime/javaThread.cpp | 4 +- src/hotspot/share/runtime/vframe.cpp | 7 +- src/hotspot/share/runtime/vframe.hpp | 4 +- .../dcmd/thread/ClassInitMonitorVThread.java | 134 ++++++++++++++++++ 4 files changed, 142 insertions(+), 7 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/dcmd/thread/ClassInitMonitorVThread.java diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 060c0a33cdf..57f93f87d47 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -1795,7 +1795,7 @@ void JavaThread::print_stack_on(outputStream* st) { // Print out lock information if (JavaMonitorsInStackTrace) { - jvf->print_lock_info_on(st, count); + jvf->print_lock_info_on(st, false/*is_virtual*/, count); } } else { // Ignore non-Java frames @@ -1837,7 +1837,7 @@ void JavaThread::print_vthread_stack_on(outputStream* st) { // Print out lock information if (JavaMonitorsInStackTrace) { - jvf->print_lock_info_on(st, count); + jvf->print_lock_info_on(st, true/*is_virtual*/, count); } } else { // Ignore non-Java frames diff --git a/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp index 723ddbbb106..a3c0c41b1b1 100644 --- a/src/hotspot/share/runtime/vframe.cpp +++ b/src/hotspot/share/runtime/vframe.cpp @@ -169,7 +169,7 @@ void javaVFrame::print_locked_object_class_name(outputStream* st, Handle obj, co } } -void javaVFrame::print_lock_info_on(outputStream* st, int frame_count) { +void javaVFrame::print_lock_info_on(outputStream* st, bool is_virtual, int frame_count) { Thread* current = Thread::current(); ResourceMark rm(current); HandleMark hm(current); @@ -204,8 +204,9 @@ void javaVFrame::print_lock_info_on(outputStream* st, int frame_count) { oop obj = thread()->current_park_blocker(); Klass* k = obj->klass(); st->print_cr("\t- %s <" INTPTR_FORMAT "> (a %s)", "parking to wait for ", p2i(obj), k->external_name()); - } - else if (thread()->osthread()->get_state() == OBJECT_WAIT) { + } else if (thread()->osthread()->get_state() == OBJECT_WAIT && + // If this is a carrier thread with mounted virtual thread this is reported for the virtual thread. + (is_virtual || !thread()->is_vthread_mounted())) { // We are waiting on an Object monitor but Object.wait() isn't the // top-frame, so we should be waiting on a Class initialization monitor. InstanceKlass* k = thread()->class_to_be_initialized(); diff --git a/src/hotspot/share/runtime/vframe.hpp b/src/hotspot/share/runtime/vframe.hpp index de3fca7246b..c8c166f3236 100644 --- a/src/hotspot/share/runtime/vframe.hpp +++ b/src/hotspot/share/runtime/vframe.hpp @@ -139,8 +139,8 @@ class javaVFrame: public vframe { // printing used during stack dumps and diagnostics static void print_locked_object_class_name(outputStream* st, Handle obj, const char* lock_state); - void print_lock_info_on(outputStream* st, int frame_count); - void print_lock_info(int frame_count) { print_lock_info_on(tty, frame_count); } + void print_lock_info_on(outputStream* st, bool is_virtual, int frame_count); + void print_lock_info(bool is_virtual, int frame_count) { print_lock_info_on(tty, is_virtual, frame_count); } #ifndef PRODUCT public: diff --git a/test/hotspot/jtreg/serviceability/dcmd/thread/ClassInitMonitorVThread.java b/test/hotspot/jtreg/serviceability/dcmd/thread/ClassInitMonitorVThread.java new file mode 100644 index 00000000000..8fd5f02a9cb --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/thread/ClassInitMonitorVThread.java @@ -0,0 +1,134 @@ +/* + * 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. + */ + +import jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.OutputAnalyzer; +import java.util.concurrent.CountDownLatch; + +/* + * @test + * @bug 8356222 + * @summary Test jcmd Thread.print command for "waiting on the Class initialization monitor" case + * @requires vm.continuations + * @library /test/lib + * @run main ClassInitMonitorVThread + */ + +class LongInitClass { + static { + ClassInitMonitorVThread.longInitClass_waiting = true; + while (ClassInitMonitorVThread.longInitClass_wait) { + try { + Thread.sleep(10); + } catch (Exception ex) { + } + } + ClassInitMonitorVThread.longInitClass_waiting = false; + } + + LongInitClass() {} +} + +public class ClassInitMonitorVThread { + static volatile boolean longInitClass_wait; + static volatile boolean longInitClass_waiting; + + public static void main(String[] args) throws InterruptedException { + try { + // 1st thread starts class initialization + longInitClass_wait = true; + longInitClass_waiting = false; + Thread vthread1 = Thread.ofVirtual().name("Loader1").start(new Loader(null)); + while (!longInitClass_waiting) { + Thread.sleep(10); + } + + // 2nd thread is blocked at class initialization + // thread state is "RUNNING", so just wait some time after the thread is ready + CountDownLatch loaderReady = new CountDownLatch(1); + Thread vthread2 = Thread.ofVirtual().name("Loader2").start(new Loader(loaderReady)); + loaderReady.await(); + + Thread.sleep(100); + // try up to 20 times to avoid failures on slow environment + for (int iter = 20; iter > 0; iter--) { + try { + verify(vthread2); + break; + } catch (RuntimeException ex) { + if (iter == 0) { + throw ex; + } + System.out.println("Failed with: " + ex.getMessage() + ", retrying..."); + System.out.println(); + } + Thread.sleep(1000); + } + } finally { + longInitClass_wait = false; + } + } + + static void verify(Thread vthread2) { + boolean silent = true; + OutputAnalyzer output = new PidJcmdExecutor().execute("Thread.print -l", silent); + String out = output.getStdout(); + String carrierPrefix = "Carrying virtual thread #" + vthread2.threadId(); + String vthreadPrefix = "Mounted virtual thread " + "#" + vthread2.threadId(); + int carrierStart = out.indexOf(carrierPrefix); + int vthreadStart = out.indexOf(vthreadPrefix); + int vthreadEnd = out.indexOf("\n\n", vthreadStart); + String carrierOut = out.substring(carrierStart, vthreadStart); + String vthreadOut = out.substring(vthreadStart, vthreadEnd); + + System.out.println("carrier: " + carrierOut); + System.out.println("vthread: " + vthreadOut); + + String waitText = "- waiting on the Class initialization monitor for LongInitClass"; + + if (!vthreadOut.contains(waitText)) { + throw new RuntimeException("Vthread does not contain the lock"); + } + if (carrierOut.contains(waitText)) { + throw new RuntimeException("Carrier does contain the lock"); + } + } + + static class Loader implements Runnable { + CountDownLatch ready; + Loader(CountDownLatch ready) { + this.ready = ready; + } + public void run() { + try { + if (ready != null) { + ready.countDown(); + } + Class myClass = Class.forName("LongInitClass"); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + } + +}