diff --git a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp index 04d39bf9cf2..547ca4e51d5 100644 --- a/src/hotspot/share/services/threadService.cpp +++ b/src/hotspot/share/services/threadService.cpp @@ -1161,9 +1161,11 @@ public: Type _type; // park blocker or an object the thread waiting on/trying to lock OopHandle _obj; + // thread that owns park blocker object when park blocker is AbstractOwnableSynchronizer + OopHandle _owner; - Blocker(Type type, OopHandle obj): _type(type), _obj(obj) {} - Blocker(): _type(NOTHING), _obj(nullptr) {} + Blocker(Type type, OopHandle obj): _type(type), _obj(obj), _owner() {} + Blocker(): _type(NOTHING), _obj(), _owner() {} bool is_empty() const { return _type == NOTHING; @@ -1198,6 +1200,7 @@ public: delete _locks; } _blocker._obj.release(oop_storage()); + _blocker._owner.release(oop_storage()); } private: @@ -1300,6 +1303,13 @@ public: oop park_blocker = java_lang_Thread::park_blocker(_thread_h()); if (park_blocker != nullptr) { _blocker = Blocker(Blocker::PARK_BLOCKER, OopHandle(oop_storage(), park_blocker)); + if (park_blocker->is_a(vmClasses::java_util_concurrent_locks_AbstractOwnableSynchronizer_klass())) { + // could be stale (unlikely in practice), but it's good enough to see deadlocks + oop ownerObj = java_util_concurrent_locks_AbstractOwnableSynchronizer::get_owner_threadObj(park_blocker); + if (ownerObj != nullptr) { + _blocker._owner = OopHandle(oop_storage(), ownerObj); + } + } } ResourceMark rm(current); @@ -1381,6 +1391,7 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic { static int _locks_offset; static int _blockerTypeOrdinal_offset; static int _blockerObject_offset; + static int _parkBlockerOwner_offset; static void compute_offsets(InstanceKlass* klass, TRAPS) { JavaClasses::compute_offset(_name_offset, klass, "name", vmSymbols::string_signature(), false); @@ -1390,6 +1401,7 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic { JavaClasses::compute_offset(_locks_offset, klass, "locks", vmSymbols::jdk_internal_vm_ThreadLock_array(), false); JavaClasses::compute_offset(_blockerTypeOrdinal_offset, klass, "blockerTypeOrdinal", vmSymbols::int_signature(), false); JavaClasses::compute_offset(_blockerObject_offset, klass, "blockerObject", vmSymbols::object_signature(), false); + JavaClasses::compute_offset(_parkBlockerOwner_offset, klass, "parkBlockerOwner", vmSymbols::thread_signature(), false); } public: static void init(InstanceKlass* klass, TRAPS) { @@ -1420,9 +1432,10 @@ public: static void set_locks(oop snapshot, oop locks) { snapshot->obj_field_put(_locks_offset, locks); } - static void set_blocker(oop snapshot, int type_ordinal, oop lock) { + static void set_blocker(oop snapshot, int type_ordinal, oop lock, oop owner) { snapshot->int_field_put(_blockerTypeOrdinal_offset, type_ordinal); snapshot->obj_field_put(_blockerObject_offset, lock); + snapshot->obj_field_put(_parkBlockerOwner_offset, owner); } }; @@ -1434,6 +1447,7 @@ int jdk_internal_vm_ThreadSnapshot::_stackTrace_offset; int jdk_internal_vm_ThreadSnapshot::_locks_offset; int jdk_internal_vm_ThreadSnapshot::_blockerTypeOrdinal_offset; int jdk_internal_vm_ThreadSnapshot::_blockerObject_offset; +int jdk_internal_vm_ThreadSnapshot::_parkBlockerOwner_offset; oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { ThreadsListHandle tlh(THREAD); @@ -1559,7 +1573,8 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) { jdk_internal_vm_ThreadSnapshot::set_stack_trace(snapshot(), trace()); jdk_internal_vm_ThreadSnapshot::set_locks(snapshot(), locks()); if (!cl._blocker.is_empty()) { - jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), cl._blocker._type, cl._blocker._obj.resolve()); + jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), + cl._blocker._type, cl._blocker._obj.resolve(), cl._blocker._owner.resolve()); } return snapshot(); } diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java index a26003a3afb..276c379a564 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java @@ -205,7 +205,10 @@ public class ThreadDumper { // park blocker Object parkBlocker = snapshot.parkBlocker(); if (parkBlocker != null) { - writer.println(" - parking to wait for " + decorateObject(parkBlocker)); + String suffix = (snapshot.parkBlockerOwner() instanceof Thread owner) + ? ", owner #" + owner.threadId() + : ""; + writer.println(" - parking to wait for " + decorateObject(parkBlocker) + suffix); } // blocked on monitor enter or Object.wait @@ -335,6 +338,9 @@ public class ThreadDumper { // parkBlocker is an object to allow for exclusiveOwnerThread in the future jsonWriter.startObject("parkBlocker"); jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker)); + if (snapshot.parkBlockerOwner() instanceof Thread owner) { + jsonWriter.writeProperty("owner", owner.threadId()); + } jsonWriter.endObject(); } diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java index 4fcbaf24d2e..357d38008d1 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java @@ -44,6 +44,8 @@ class ThreadSnapshot { // an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of() private int blockerTypeOrdinal; private Object blockerObject; + // the owner of the blockerObject when the object is park blocker and is AbstractOwnableSynchronizer + private Thread parkBlockerOwner; // set by ThreadSnapshot.of() private ThreadBlocker blocker; @@ -70,8 +72,11 @@ class ThreadSnapshot { snapshot.locks = EMPTY_LOCKS; } if (snapshot.blockerObject != null) { - snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject); + snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, + snapshot.blockerObject, + snapshot.parkBlockerOwner); snapshot.blockerObject = null; // release + snapshot.parkBlockerOwner = null; } return snapshot; } @@ -104,6 +109,13 @@ class ThreadSnapshot { return getBlocker(BlockerLockType.PARK_BLOCKER); } + /** + * Returns the owner of the parkBlocker if the parkBlocker is an AbstractOwnableSynchronizer. + */ + Thread parkBlockerOwner() { + return (blocker != null && blocker.type == BlockerLockType.PARK_BLOCKER) ? blocker.owner : null; + } + /** * Returns the object that the thread is blocked on. * @throws IllegalStateException if not in the blocked state @@ -211,11 +223,11 @@ class ThreadSnapshot { } } - private record ThreadBlocker(BlockerLockType type, Object obj) { + private record ThreadBlocker(BlockerLockType type, Object obj, Thread owner) { private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache - ThreadBlocker(int typeOrdinal, Object obj) { - this(lockTypeValues[typeOrdinal], obj); + ThreadBlocker(int typeOrdinal, Object obj, Thread owner) { + this(lockTypeValues[typeOrdinal], obj, owner); } } diff --git a/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json index 57ef5c8b859..bf52bb3915d 100644 --- a/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json +++ b/src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json @@ -78,6 +78,10 @@ "description": "The blocker object responsible for the thread parking." } }, + "owner": { + "type": "string", + "description": "The thread identifier of the owner when the parkBlocker is an AbstractOwnableSynchronizer." + } "required": [ "object" ] @@ -115,9 +119,9 @@ "items": { "type": [ "string", - null + "null" ], - "description": "The object for which the monitor is owned by the thread, null if eliminated" + "description": "The object for which the monitor is owned by the thread, null if eliminated." } } }, diff --git a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java index adf643749c7..77020491c29 100644 --- a/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java +++ b/test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8284161 8287008 8309406 8356870 + * @bug 8284161 8287008 8309406 8356870 8365057 * @summary Basic test for com.sun.management.HotSpotDiagnosticMXBean.dumpThreads * @requires vm.continuations * @modules java.base/jdk.internal.vm jdk.management @@ -425,7 +425,9 @@ class DumpThreads { ThreadFields fields = findThread(tid, lines); assertNotNull(fields, "thread not found"); assertEquals("WAITING", fields.state()); - assertTrue(contains(lines, "- parking to wait for l.contains(text)); } + /** + * Finds the line of a plain text thread dump containing the given text. + */ + private String find(List lines, String text) { + return lines.stream().map(String::trim) + .filter(l -> l.contains(text)) + .findAny() + .orElse(null); + } + /** * Dump threads to a file in plain text format, return the lines in the file. */ diff --git a/test/lib/jdk/test/lib/threaddump/ThreadDump.java b/test/lib/jdk/test/lib/threaddump/ThreadDump.java index f4964a9521f..ca728e625fc 100644 --- a/test/lib/jdk/test/lib/threaddump/ThreadDump.java +++ b/test/lib/jdk/test/lib/threaddump/ThreadDump.java @@ -296,6 +296,16 @@ public final class ThreadDump { return getStringProperty("parkBlocker", "object"); } + /** + * Returns the owner of the parkBlocker if the parkBlocker is an AbstractOwnableSynchronizer. + */ + public OptionalLong parkBlockerOwner() { + String owner = getStringProperty("parkBlocker", "owner"); + return (owner != null) + ? OptionalLong.of(Long.parseLong(owner)) + : OptionalLong.empty(); + } + /** * Returns the object that the thread is blocked entering its monitor. */