mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8365057: Add support for java.util.concurrent lock information to Thread.dump_to_file
Co-authored-by: Alex Menkov <amenkov@openjdk.org> Co-authored-by: Alan Bateman <alanb@openjdk.org> Reviewed-by: sspitsyn, alanb
This commit is contained in:
parent
12c0f29b97
commit
cedc0117ac
@ -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();
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -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 <java.util.concurrent.locks.ReentrantLock"));
|
||||
String line = find(lines, "- parking to wait for <java.util.concurrent.locks.ReentrantLock");
|
||||
assertNotNull(line, "parking to wait line not found");
|
||||
assertTrue(line.endsWith(" owner #" + Thread.currentThread().threadId()));
|
||||
|
||||
// thread dump in JSON format should include thread in root container
|
||||
ThreadDump threadDump = dumpThreadsToJson();
|
||||
@ -440,6 +442,12 @@ class DumpThreads {
|
||||
String parkBlocker = ti.parkBlocker();
|
||||
assertNotNull(parkBlocker);
|
||||
assertTrue(parkBlocker.contains("java.util.concurrent.locks.ReentrantLock"));
|
||||
|
||||
// the owner of the parkBlocker should be the current thread
|
||||
long ownerTid = ti.parkBlockerOwner().orElse(-1L);
|
||||
assertNotEquals(-1L, ownerTid, "parkBlockerOwner not found");
|
||||
assertEquals(Thread.currentThread().threadId(), ownerTid);
|
||||
|
||||
if (pinned) {
|
||||
long carrierTid = ti.carrier().orElse(-1L);
|
||||
assertNotEquals(-1L, carrierTid, "carrier not found");
|
||||
@ -684,6 +692,16 @@ class DumpThreads {
|
||||
.anyMatch(l -> l.contains(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the line of a plain text thread dump containing the given text.
|
||||
*/
|
||||
private String find(List<String> 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.
|
||||
*/
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user