From 98b667834c4a8f0d0ab54b8133061bc475674732 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Fri, 22 Nov 2024 19:21:09 +0000 Subject: [PATCH] 8343741: SA jstack --mixed should print information about VM locks Reviewed-by: cjplummer --- src/hotspot/share/prims/whitebox.cpp | 11 +++ src/hotspot/share/runtime/mutex.cpp | 65 ++++++++++++++ src/hotspot/share/runtime/mutex.hpp | 11 +++ src/hotspot/share/runtime/mutexLocker.cpp | 68 +-------------- src/hotspot/share/runtime/mutexLocker.hpp | 5 -- src/hotspot/share/runtime/vmOperations.hpp | 12 +++ src/hotspot/share/runtime/vmStructs.cpp | 10 ++- src/hotspot/share/utilities/vmError.cpp | 2 +- .../sun/jvm/hotspot/runtime/Mutex.java | 75 ++++++++++++++++ .../sun/jvm/hotspot/runtime/Thread.java | 9 +- .../jvm/hotspot/runtime/VMLocksPrinter.java | 70 +++++++++++++++ .../classes/sun/jvm/hotspot/tools/PStack.java | 7 ++ test/hotspot/jtreg/ProblemList-zgc.txt | 1 + .../jtreg/serviceability/sa/ClhsdbField.java | 4 +- .../sa/ClhsdbVmStructsDump.java | 4 +- .../sa/LingeredAppWithLockInVM.java | 55 ++++++++++++ .../sa/TestJhsdbJstackPrintVMLocks.java | 85 +++++++++++++++++++ test/lib/jdk/test/lib/apps/LingeredApp.java | 7 ++ test/lib/jdk/test/whitebox/WhiteBox.java | 2 + 19 files changed, 424 insertions(+), 79 deletions(-) create mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Mutex.java create mode 100644 src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VMLocksPrinter.java create mode 100644 test/hotspot/jtreg/serviceability/sa/LingeredAppWithLockInVM.java create mode 100644 test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index a8607bb2efd..93c1acea1db 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -186,6 +186,16 @@ WB_ENTRY(jstring, WB_PrintString(JNIEnv* env, jobject wb, jstring str, jint max_ return (jstring) JNIHandles::make_local(THREAD, result); WB_END +WB_ENTRY(jint, WB_TakeLockAndHangInSafepoint(JNIEnv* env, jobject wb)) + JavaThread* self = JavaThread::current(); + // VMStatistic_lock is used to minimize interference with VM locking + MutexLocker mu(VMStatistic_lock); + VM_HangInSafepoint force_safepoint_stuck_op; + VMThread::execute(&force_safepoint_stuck_op); + ShouldNotReachHere(); + return 0; +WB_END + class WBIsKlassAliveClosure : public LockedClassesDo { Symbol* _name; int _count; @@ -2988,6 +2998,7 @@ static JNINativeMethod methods[] = { {CC"cleanMetaspaces", CC"()V", (void*)&WB_CleanMetaspaces}, {CC"rss", CC"()J", (void*)&WB_Rss}, {CC"printString", CC"(Ljava/lang/String;I)Ljava/lang/String;", (void*)&WB_PrintString}, + {CC"lockAndStuckInSafepoint", CC"()V", (void*)&WB_TakeLockAndHangInSafepoint}, {CC"wordSize", CC"()J", (void*)&WB_WordSize}, {CC"rootChunkWordSize", CC"()J", (void*)&WB_RootChunkWordSize} }; diff --git a/src/hotspot/share/runtime/mutex.cpp b/src/hotspot/share/runtime/mutex.cpp index 6466d18e538..0b1a79307f1 100644 --- a/src/hotspot/share/runtime/mutex.cpp +++ b/src/hotspot/share/runtime/mutex.cpp @@ -267,6 +267,16 @@ bool Monitor::wait(uint64_t timeout) { return wait_status != 0; // return true IFF timeout } +static const int MAX_NUM_MUTEX = 1204; +static Mutex* _internal_mutex_arr[MAX_NUM_MUTEX]; +Mutex** Mutex::_mutex_array = _internal_mutex_arr; +int Mutex::_num_mutex = 0; + +void Mutex::add_mutex(Mutex* var) { + assert(Mutex::_num_mutex < MAX_NUM_MUTEX, "increase MAX_NUM_MUTEX"); + Mutex::_mutex_array[_num_mutex++] = var; +} + Mutex::~Mutex() { assert_owner(nullptr); os::free(const_cast(_name)); @@ -524,6 +534,61 @@ void Mutex::set_owner_implementation(Thread *new_owner) { } #endif // ASSERT +// Print all mutexes/monitors that are currently owned by a thread; called +// by fatal error handler. +void Mutex::print_owned_locks_on_error(outputStream* st) { + st->print("VM Mutex/Monitor currently owned by a thread: "); + bool none = true; + for (int i = 0; i < _num_mutex; i++) { + // see if it has an owner + if (_mutex_array[i]->owner() != nullptr) { + if (none) { + // print format used by Mutex::print_on_error() + st->print_cr(" ([mutex/lock_event])"); + none = false; + } + _mutex_array[i]->print_on_error(st); + st->cr(); + } + } + if (none) st->print_cr("None"); +} + +void Mutex::print_lock_ranks(outputStream* st) { + st->print_cr("VM Mutex/Monitor ranks: "); + +#ifdef ASSERT + // Be extra defensive and figure out the bounds on + // ranks right here. This also saves a bit of time + // in the #ranks*#mutexes loop below. + int min_rank = INT_MAX; + int max_rank = INT_MIN; + for (int i = 0; i < _num_mutex; i++) { + Mutex* m = _mutex_array[i]; + int r = (int) m->rank(); + if (min_rank > r) min_rank = r; + if (max_rank < r) max_rank = r; + } + + // Print the listings rank by rank + for (int r = min_rank; r <= max_rank; r++) { + bool first = true; + for (int i = 0; i < _num_mutex; i++) { + Mutex* m = _mutex_array[i]; + if (r != (int) m->rank()) continue; + + if (first) { + st->cr(); + st->print_cr("Rank \"%s\":", m->rank_name()); + first = false; + } + st->print_cr(" %s", m->name()); + } + } +#else + st->print_cr(" Only known in debug builds."); +#endif // ASSERT +} RecursiveMutex::RecursiveMutex() : _sem(1), _owner(nullptr), _recursions(0) {} diff --git a/src/hotspot/share/runtime/mutex.hpp b/src/hotspot/share/runtime/mutex.hpp index 8818447e5dd..5ed73507150 100644 --- a/src/hotspot/share/runtime/mutex.hpp +++ b/src/hotspot/share/runtime/mutex.hpp @@ -51,6 +51,7 @@ class Mutex : public CHeapObj { + friend class VMStructs; public: // Special low level locks are given names and ranges avoid overlap. enum class Rank { @@ -103,6 +104,9 @@ class Mutex : public CHeapObj { #ifndef PRODUCT bool _allow_vm_block; #endif + static Mutex** _mutex_array; + static int _num_mutex; + #ifdef ASSERT Rank _rank; // rank (to avoid/detect potential deadlocks) Mutex* _next; // Used by a Thread to link up owned locks @@ -194,11 +198,18 @@ class Mutex : public CHeapObj { const char *name() const { return _name; } + static void add_mutex(Mutex* var); + void print_on_error(outputStream* st) const; #ifndef PRODUCT void print_on(outputStream* st) const; void print() const; #endif + + // Print all mutexes/monitors that are currently owned by a thread; called + // by fatal error handler. + static void print_owned_locks_on_error(outputStream* st); + static void print_lock_ranks(outputStream* st); }; class Monitor : public Mutex { diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index a0a6e5626e4..3d5b49f56ce 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -158,10 +158,6 @@ Monitor* JVMCIRuntime_lock = nullptr; // Only one RecursiveMutex RecursiveMutex* MultiArray_lock = nullptr; -#define MAX_NUM_MUTEX 128 -static Mutex* _mutex_array[MAX_NUM_MUTEX]; -static int _num_mutex; - #ifdef ASSERT void assert_locked_or_safepoint(const Mutex* lock) { if (DebuggingContext::is_enabled() || VMError::is_error_reported()) return; @@ -182,18 +178,13 @@ void assert_lock_strong(const Mutex* lock) { } #endif -static void add_mutex(Mutex* var) { - assert(_num_mutex < MAX_NUM_MUTEX, "increase MAX_NUM_MUTEX"); - _mutex_array[_num_mutex++] = var; -} - #define MUTEX_STORAGE_NAME(name) name##_storage #define MUTEX_STORAGE(name, type) alignas(type) static uint8_t MUTEX_STORAGE_NAME(name)[sizeof(type)] #define MUTEX_DEF(name, type, pri, ...) { \ assert(name == nullptr, "Mutex/Monitor initialized twice"); \ MUTEX_STORAGE(name, type); \ name = ::new(static_cast(MUTEX_STORAGE_NAME(name))) type((pri), #name, ##__VA_ARGS__); \ - add_mutex(name); \ + Mutex::add_mutex(name); \ } #define MUTEX_DEFN(name, type, pri, ...) MUTEX_DEF(name, type, Mutex::pri, ##__VA_ARGS__) @@ -371,7 +362,7 @@ void MutexLockerImpl::post_initialize() { if (lt.is_enabled()) { ResourceMark rm; LogStream ls(lt); - print_lock_ranks(&ls); + Mutex::print_lock_ranks(&ls); } } @@ -385,58 +376,3 @@ GCMutexLocker::GCMutexLocker(Mutex* mutex) { } } -// Print all mutexes/monitors that are currently owned by a thread; called -// by fatal error handler. -void print_owned_locks_on_error(outputStream* st) { - st->print("VM Mutex/Monitor currently owned by a thread: "); - bool none = true; - for (int i = 0; i < _num_mutex; i++) { - // see if it has an owner - if (_mutex_array[i]->owner() != nullptr) { - if (none) { - // print format used by Mutex::print_on_error() - st->print_cr(" ([mutex/lock_event])"); - none = false; - } - _mutex_array[i]->print_on_error(st); - st->cr(); - } - } - if (none) st->print_cr("None"); -} - -void print_lock_ranks(outputStream* st) { - st->print_cr("VM Mutex/Monitor ranks: "); - -#ifdef ASSERT - // Be extra defensive and figure out the bounds on - // ranks right here. This also saves a bit of time - // in the #ranks*#mutexes loop below. - int min_rank = INT_MAX; - int max_rank = INT_MIN; - for (int i = 0; i < _num_mutex; i++) { - Mutex* m = _mutex_array[i]; - int r = (int) m->rank(); - if (min_rank > r) min_rank = r; - if (max_rank < r) max_rank = r; - } - - // Print the listings rank by rank - for (int r = min_rank; r <= max_rank; r++) { - bool first = true; - for (int i = 0; i < _num_mutex; i++) { - Mutex* m = _mutex_array[i]; - if (r != (int) m->rank()) continue; - - if (first) { - st->cr(); - st->print_cr("Rank \"%s\":", m->rank_name()); - first = false; - } - st->print_cr(" %s", m->name()); - } - } -#else - st->print_cr(" Only known in debug builds."); -#endif // ASSERT -} diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp index 7cca1d94bf2..0e0c0435443 100644 --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -171,11 +171,6 @@ extern Mutex* tty_lock; // lock to synchronize output // order. If their implementations change such that these assumptions // are violated, a whole lot of code will break. -// Print all mutexes/monitors that are currently owned by a thread; called -// by fatal error handler. -void print_owned_locks_on_error(outputStream* st); -void print_lock_ranks(outputStream* st); - // for debugging: check that we're already owning this lock (or are at a safepoint / handshake) #ifdef ASSERT void assert_locked_or_safepoint(const Mutex* lock); diff --git a/src/hotspot/share/runtime/vmOperations.hpp b/src/hotspot/share/runtime/vmOperations.hpp index ea7f62df37d..baeea722dce 100644 --- a/src/hotspot/share/runtime/vmOperations.hpp +++ b/src/hotspot/share/runtime/vmOperations.hpp @@ -60,6 +60,18 @@ class VM_ForceSafepoint: public VM_EmptyOperation { VMOp_Type type() const { return VMOp_ForceSafepoint; } }; +// used by whitebox API to emulate VM issues +// when VM can't operate and doesn't respond to jcmd +class VM_HangInSafepoint: public VM_Operation { +public: + VMOp_Type type() const { return VMOp_ForceSafepoint; } + void doit() { + while(true) { + os::naked_short_sleep(10); + } + } +}; + class VM_ClearICs: public VM_Operation { private: bool _preserve_static_stubs; diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index a1c1551ae09..6fa57acf3ce 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -659,7 +659,6 @@ volatile_nonstatic_field(JavaThread, _is_method_handle_return, int) \ nonstatic_field(JavaThread, _saved_exception_pc, address) \ volatile_nonstatic_field(JavaThread, _thread_state, JavaThreadState) \ - nonstatic_field(JavaThread, _osthread, OSThread*) \ nonstatic_field(JavaThread, _stack_base, address) \ nonstatic_field(JavaThread, _stack_size, size_t) \ nonstatic_field(JavaThread, _vframe_array_head, vframeArray*) \ @@ -667,6 +666,7 @@ nonstatic_field(JavaThread, _active_handles, JNIHandleBlock*) \ nonstatic_field(JavaThread, _lock_id, int64_t) \ volatile_nonstatic_field(JavaThread, _terminated, JavaThread::TerminatedTypes) \ + nonstatic_field(Thread, _osthread, OSThread*) \ nonstatic_field(Thread, _resource_area, ResourceArea*) \ nonstatic_field(CompilerThread, _env, ciEnv*) \ \ @@ -1022,7 +1022,12 @@ nonstatic_field(elapsedTimer, _active, bool) \ nonstatic_field(InvocationCounter, _counter, unsigned int) \ \ - nonstatic_field(UpcallStub::FrameData, jfa, JavaFrameAnchor) + nonstatic_field(UpcallStub::FrameData, jfa, JavaFrameAnchor) \ + \ + nonstatic_field(Mutex, _name, const char*) \ + static_field(Mutex, _mutex_array, Mutex**) \ + static_field(Mutex, _num_mutex, int) \ + volatile_nonstatic_field(Mutex, _owner, Thread*) //-------------------------------------------------------------------------------- // VM_TYPES @@ -1936,6 +1941,7 @@ declare_toplevel_type(JNIid) \ declare_toplevel_type(JNIid*) \ declare_toplevel_type(jmethodID*) \ + declare_toplevel_type(Mutex) \ declare_toplevel_type(Mutex*) \ declare_toplevel_type(nmethod*) \ COMPILER2_PRESENT(declare_unsigned_integer_type(node_idx_t)) \ diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 23db59f766a..cdece538cba 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -1223,7 +1223,7 @@ void VMError::report(outputStream* st, bool _verbose) { STEP_IF("printing owned locks on error", _verbose) // mutexes/monitors that currently have an owner - print_owned_locks_on_error(st); + Mutex::print_owned_locks_on_error(st); st->cr(); STEP_IF("printing number of OutOfMemoryError and StackOverflow exceptions", diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Mutex.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Mutex.java new file mode 100644 index 00000000000..bcd08f0d52c --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Mutex.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +package sun.jvm.hotspot.runtime; + +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.types.AddressField; +import sun.jvm.hotspot.types.Type; +import sun.jvm.hotspot.types.TypeDataBase; +import sun.jvm.hotspot.types.WrongTypeException; +import sun.jvm.hotspot.utilities.*; + +public class Mutex extends VMObject { + private static long nameFieldOffset; + private static long ownerFieldOffset; + + private static AddressField mutex_array; + private static int maxNum; + + private static final long addrSize = VM.getVM().getAddressSize(); + + static { + VM.registerVMInitializedObserver(new Observer() { + public void update(Observable o, Object data) { + initialize(VM.getVM().getTypeDataBase()); + } + }); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + Type type = db.lookupType("Mutex"); + + sun.jvm.hotspot.types.Field nameField = type.getField("_name"); + nameFieldOffset = nameField.getOffset(); + sun.jvm.hotspot.types.Field ownerField = type.getField("_owner"); + ownerFieldOffset = ownerField.getOffset(); + + mutex_array = type.getAddressField("_mutex_array"); + maxNum = type.getCIntegerField("_num_mutex").getJInt(); + } + + public Mutex(Address addr) { + super(addr); + } + + public String name() { return CStringUtilities.getString(addr.getAddressAt(nameFieldOffset)); } + + public Address owner() { return addr.getAddressAt(ownerFieldOffset); } + + public static Address at(int i) { return mutex_array.getValue().getAddressAt(i * addrSize); } + + public static int maxNum() { return maxNum; } + +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Thread.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Thread.java index 8267f12a9e9..d9828b06e26 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Thread.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Thread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, 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 @@ -37,6 +37,7 @@ public class Thread extends VMObject { private static AddressField currentPendingMonitorField; private static AddressField currentWaitingMonitorField; + private static AddressField osThreadField; private static JLongField allocatedBytesField; @@ -53,6 +54,8 @@ public class Thread extends VMObject { Type typeJavaThread = db.lookupType("JavaThread"); suspendFlagsField = typeJavaThread.getCIntegerField("_suspend_flags"); + osThreadField = typeThread.getAddressField("_osthread"); + tlabFieldOffset = typeThread.getField("_tlab").getOffset(); currentPendingMonitorField = typeJavaThread.getAddressField("_current_pending_monitor"); @@ -123,6 +126,10 @@ public class Thread extends VMObject { return false; } + public OSThread osThread() { + return new OSThread(osThreadField.getValue(addr)); + } + /** Assistance for ObjectMonitor implementation */ Address threadObjectAddress() { return addr; } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VMLocksPrinter.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VMLocksPrinter.java new file mode 100644 index 00000000000..4fa0feeb6ce --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/VMLocksPrinter.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, 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. + * + */ + +package sun.jvm.hotspot.runtime; + +import sun.jvm.hotspot.debugger.Address; +import sun.jvm.hotspot.memory.SystemDictionary; +import sun.jvm.hotspot.oops.DefaultHeapVisitor; +import sun.jvm.hotspot.oops.Klass; +import sun.jvm.hotspot.oops.ObjectHeap; +import sun.jvm.hotspot.oops.Oop; +import sun.jvm.hotspot.oops.OopUtilities; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VMLocksPrinter { + private PrintStream tty; + private Threads threads = VM.getVM().getThreads(); + + public VMLocksPrinter(PrintStream tty) { + this.tty = tty; + } + + private String ownerThreadName(Address addr) { + try { + JavaThread thread = VM.getVM().getThreads().createJavaThreadWrapper(addr); + return thread.getThreadName(); + } catch (Exception e) { + return "Unknown thread"; + } + } + + public void printVMLocks() { + int maxNum = Mutex.maxNum(); + for (int i = 0; i < maxNum; i++) { + Mutex mutex = new Mutex(Mutex.at(i)); + if (mutex.owner() != null) { + sun.jvm.hotspot.runtime.Thread t = new sun.jvm.hotspot.runtime.Thread(mutex.owner()); + int nativeThreadId = t.osThread().threadId(); + tty.println("Internal VM Mutex " + mutex.name() + " is owned by " + ownerThreadName(mutex.owner()) + + ", nid=" + nativeThreadId + ", address=" + mutex.owner()); + } + } + } +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java index 8f09dcc48ef..2f48c75eb78 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/tools/PStack.java @@ -81,6 +81,13 @@ public class PStack extends Tool { out.println("can't print deadlock information: " + exp); } + try { + VMLocksPrinter vmLocksPrinter = new VMLocksPrinter(out); + vmLocksPrinter.printVMLocks(); + } catch (Exception e) { + out.println("can't print VM locks information: " + e); + } + List l = cdbg.getThreadList(); if (l.isEmpty() && PlatformInfo.getOS().equals("darwin")) { // If the list is empty, we assume we attached to a process, and on OSX we can only diff --git a/test/hotspot/jtreg/ProblemList-zgc.txt b/test/hotspot/jtreg/ProblemList-zgc.txt index 0d4cfe1cbb6..1366096f346 100644 --- a/test/hotspot/jtreg/ProblemList-zgc.txt +++ b/test/hotspot/jtreg/ProblemList-zgc.txt @@ -92,6 +92,7 @@ serviceability/sa/TestIntConstant.java 8307393 generic- serviceability/sa/TestJhsdbJstackLineNumbers.java 8307393 generic-all serviceability/sa/TestJhsdbJstackLock.java 8307393 generic-all serviceability/sa/TestJhsdbJstackMixed.java 8307393 generic-all +serviceability/sa/TestJhsdbJstackPrintVMLocks.java 8307393 generic-all serviceability/sa/TestJhsdbJstackUpcall.java 8307393 generic-all serviceability/sa/TestJmapCore.java 8307393 generic-all serviceability/sa/TestJmapCoreMetaspace.java 8307393 generic-all diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbField.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbField.java index 2ed557f8e94..18f40603505 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbField.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbField.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, 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 @@ -55,7 +55,7 @@ public class ClhsdbField { "field InstanceKlass _methods Array*", "field InstanceKlass _constants ConstantPool*", "field Klass _name Symbol*", - "field JavaThread _osthread OSThread*", + "field Thread _osthread OSThread*", "field TenuredGeneration _the_space ContiguousSpace*", "field VirtualSpace _low_boundary char*", "field MethodCounters _backedge_counter InvocationCounter", diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbVmStructsDump.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbVmStructsDump.java index 050ec9c05d6..8ca5b5e6e38 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbVmStructsDump.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbVmStructsDump.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, 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 @@ -56,7 +56,7 @@ public class ClhsdbVmStructsDump { "field InstanceKlass _constants ConstantPool*", "field Klass _name Symbol*", "type ClassLoaderData* null", - "field JavaThread _osthread OSThread*", + "field Thread _osthread OSThread*", "type TenuredGeneration Generation", "type Universe null", "type ConstantPoolCache MetaspaceObj")); diff --git a/test/hotspot/jtreg/serviceability/sa/LingeredAppWithLockInVM.java b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithLockInVM.java new file mode 100644 index 00000000000..686cdfc4f2b --- /dev/null +++ b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithLockInVM.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, 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.whitebox.WhiteBox; +import jdk.test.lib.apps.LingeredApp; + +public class LingeredAppWithLockInVM extends LingeredApp { + + private static class LockerThread implements Runnable { + public void run() { + while (!isReady()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + WhiteBox wb = WhiteBox.getWhiteBox(); + wb.lockAndStuckInSafepoint(); + } + } + + + public static void main(String args[]) { + if (args.length != 1) { + System.err.println("Lock file name is not specified"); + System.exit(7); + } + + Thread t = new Thread(new LockerThread()); + t.setName("LockerThread"); + t.start(); + + LingeredApp.main(args); + } + } diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java new file mode 100644 index 00000000000..ff1cc4b96bd --- /dev/null +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, 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.JDKToolLauncher; +import jdk.test.lib.SA.SATestUtils; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; + +/** + * @test + * @summary Test verifies that jstack --mixed prints information about VM locks + * @requires vm.hasSA + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver TestJhsdbJstackPrintVMLocks + */ + +public class TestJhsdbJstackPrintVMLocks { + + final static int MAX_ATTEMPTS = 5; + public static void main(String[] args) throws Exception { + SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work. + + LingeredApp theApp = null; + try { + theApp = new LingeredAppWithLockInVM(); + LingeredApp.startApp(theApp, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-Xbootclasspath/a:."); + + System.out.println("Started LingeredApp with pid " + theApp.getPid()); + theApp.waitAppReadyOrCrashed(); + + for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) { + JDKToolLauncher launcher = JDKToolLauncher + .createUsingTestJDK("jhsdb"); + launcher.addToolArg("jstack"); + launcher.addToolArg("--mixed"); + launcher.addToolArg("--pid"); + launcher.addToolArg(Long.toString(theApp.getPid())); + + ProcessBuilder pb = SATestUtils.createProcessBuilder(launcher); + Process jhsdb = pb.start(); + OutputAnalyzer out = new OutputAnalyzer(jhsdb); + + jhsdb.waitFor(); + + System.out.println(out.getStdout()); + System.err.println(out.getStderr()); + + if (out.contains("Mutex VMStatistic_lock is owned by LockerThread")) { + System.out.println("Test PASSED"); + return; + } + Thread.sleep(attempt * 2000); + } + throw new RuntimeException("Not able to find lock"); + } finally { + theApp.getProcess().destroyForcibly(); + } + } +} diff --git a/test/lib/jdk/test/lib/apps/LingeredApp.java b/test/lib/jdk/test/lib/apps/LingeredApp.java index d0b97b695d0..73904b81848 100644 --- a/test/lib/jdk/test/lib/apps/LingeredApp.java +++ b/test/lib/jdk/test/lib/apps/LingeredApp.java @@ -589,6 +589,12 @@ public class LingeredApp { static class SteadyStateLock {}; + private static volatile boolean isReady = false; + + protected static boolean isReady() { + return isReady; + } + /** * This part is the application itself. First arg is optional "forceCrash". * Following arg is the lock file name. @@ -627,6 +633,7 @@ public class LingeredApp { while (Files.exists(path)) { // Touch the lock to indicate our readiness setLastModified(theLockFileName, epoch()); + isReady = true; Thread.sleep(spinDelay); } } diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 2e723d91267..f68eb978912 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -99,6 +99,8 @@ public class WhiteBox { // printed by the VM. public native String printString(String str, int maxLength); + public native void lockAndStuckInSafepoint(); + public int countAliveClasses(String name) { // Make sure class name is in the correct format return countAliveClasses0(name.replace('.', '/'));