Skip to content

Commit cedc011

Browse files
Alex MenkovAlan Bateman
andcommitted
8365057: Add support for java.util.concurrent lock information to Thread.dump_to_file
Co-authored-by: Alex Menkov <[email protected]> Co-authored-by: Alan Bateman <[email protected]> Reviewed-by: sspitsyn, alanb
1 parent 12c0f29 commit cedc011

File tree

6 files changed

+78
-13
lines changed

6 files changed

+78
-13
lines changed

src/hotspot/share/services/threadService.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,9 +1161,11 @@ class GetThreadSnapshotHandshakeClosure: public HandshakeClosure {
11611161
Type _type;
11621162
// park blocker or an object the thread waiting on/trying to lock
11631163
OopHandle _obj;
1164+
// thread that owns park blocker object when park blocker is AbstractOwnableSynchronizer
1165+
OopHandle _owner;
11641166

1165-
Blocker(Type type, OopHandle obj): _type(type), _obj(obj) {}
1166-
Blocker(): _type(NOTHING), _obj(nullptr) {}
1167+
Blocker(Type type, OopHandle obj): _type(type), _obj(obj), _owner() {}
1168+
Blocker(): _type(NOTHING), _obj(), _owner() {}
11671169

11681170
bool is_empty() const {
11691171
return _type == NOTHING;
@@ -1198,6 +1200,7 @@ class GetThreadSnapshotHandshakeClosure: public HandshakeClosure {
11981200
delete _locks;
11991201
}
12001202
_blocker._obj.release(oop_storage());
1203+
_blocker._owner.release(oop_storage());
12011204
}
12021205

12031206
private:
@@ -1300,6 +1303,13 @@ class GetThreadSnapshotHandshakeClosure: public HandshakeClosure {
13001303
oop park_blocker = java_lang_Thread::park_blocker(_thread_h());
13011304
if (park_blocker != nullptr) {
13021305
_blocker = Blocker(Blocker::PARK_BLOCKER, OopHandle(oop_storage(), park_blocker));
1306+
if (park_blocker->is_a(vmClasses::java_util_concurrent_locks_AbstractOwnableSynchronizer_klass())) {
1307+
// could be stale (unlikely in practice), but it's good enough to see deadlocks
1308+
oop ownerObj = java_util_concurrent_locks_AbstractOwnableSynchronizer::get_owner_threadObj(park_blocker);
1309+
if (ownerObj != nullptr) {
1310+
_blocker._owner = OopHandle(oop_storage(), ownerObj);
1311+
}
1312+
}
13031313
}
13041314

13051315
ResourceMark rm(current);
@@ -1381,6 +1391,7 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic {
13811391
static int _locks_offset;
13821392
static int _blockerTypeOrdinal_offset;
13831393
static int _blockerObject_offset;
1394+
static int _parkBlockerOwner_offset;
13841395

13851396
static void compute_offsets(InstanceKlass* klass, TRAPS) {
13861397
JavaClasses::compute_offset(_name_offset, klass, "name", vmSymbols::string_signature(), false);
@@ -1390,6 +1401,7 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic {
13901401
JavaClasses::compute_offset(_locks_offset, klass, "locks", vmSymbols::jdk_internal_vm_ThreadLock_array(), false);
13911402
JavaClasses::compute_offset(_blockerTypeOrdinal_offset, klass, "blockerTypeOrdinal", vmSymbols::int_signature(), false);
13921403
JavaClasses::compute_offset(_blockerObject_offset, klass, "blockerObject", vmSymbols::object_signature(), false);
1404+
JavaClasses::compute_offset(_parkBlockerOwner_offset, klass, "parkBlockerOwner", vmSymbols::thread_signature(), false);
13931405
}
13941406
public:
13951407
static void init(InstanceKlass* klass, TRAPS) {
@@ -1420,9 +1432,10 @@ class jdk_internal_vm_ThreadSnapshot: AllStatic {
14201432
static void set_locks(oop snapshot, oop locks) {
14211433
snapshot->obj_field_put(_locks_offset, locks);
14221434
}
1423-
static void set_blocker(oop snapshot, int type_ordinal, oop lock) {
1435+
static void set_blocker(oop snapshot, int type_ordinal, oop lock, oop owner) {
14241436
snapshot->int_field_put(_blockerTypeOrdinal_offset, type_ordinal);
14251437
snapshot->obj_field_put(_blockerObject_offset, lock);
1438+
snapshot->obj_field_put(_parkBlockerOwner_offset, owner);
14261439
}
14271440
};
14281441

@@ -1434,6 +1447,7 @@ int jdk_internal_vm_ThreadSnapshot::_stackTrace_offset;
14341447
int jdk_internal_vm_ThreadSnapshot::_locks_offset;
14351448
int jdk_internal_vm_ThreadSnapshot::_blockerTypeOrdinal_offset;
14361449
int jdk_internal_vm_ThreadSnapshot::_blockerObject_offset;
1450+
int jdk_internal_vm_ThreadSnapshot::_parkBlockerOwner_offset;
14371451

14381452
oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) {
14391453
ThreadsListHandle tlh(THREAD);
@@ -1559,7 +1573,8 @@ oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) {
15591573
jdk_internal_vm_ThreadSnapshot::set_stack_trace(snapshot(), trace());
15601574
jdk_internal_vm_ThreadSnapshot::set_locks(snapshot(), locks());
15611575
if (!cl._blocker.is_empty()) {
1562-
jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), cl._blocker._type, cl._blocker._obj.resolve());
1576+
jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(),
1577+
cl._blocker._type, cl._blocker._obj.resolve(), cl._blocker._owner.resolve());
15631578
}
15641579
return snapshot();
15651580
}

src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,10 @@ private static boolean dumpThread(Thread thread, TextWriter writer) {
205205
// park blocker
206206
Object parkBlocker = snapshot.parkBlocker();
207207
if (parkBlocker != null) {
208-
writer.println(" - parking to wait for " + decorateObject(parkBlocker));
208+
String suffix = (snapshot.parkBlockerOwner() instanceof Thread owner)
209+
? ", owner #" + owner.threadId()
210+
: "";
211+
writer.println(" - parking to wait for " + decorateObject(parkBlocker) + suffix);
209212
}
210213

211214
// blocked on monitor enter or Object.wait
@@ -335,6 +338,9 @@ private static boolean dumpThread(Thread thread, JsonWriter jsonWriter) {
335338
// parkBlocker is an object to allow for exclusiveOwnerThread in the future
336339
jsonWriter.startObject("parkBlocker");
337340
jsonWriter.writeProperty("object", Objects.toIdentityString(parkBlocker));
341+
if (snapshot.parkBlockerOwner() instanceof Thread owner) {
342+
jsonWriter.writeProperty("owner", owner.threadId());
343+
}
338344
jsonWriter.endObject();
339345
}
340346

src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class ThreadSnapshot {
4444
// an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of()
4545
private int blockerTypeOrdinal;
4646
private Object blockerObject;
47+
// the owner of the blockerObject when the object is park blocker and is AbstractOwnableSynchronizer
48+
private Thread parkBlockerOwner;
4749

4850
// set by ThreadSnapshot.of()
4951
private ThreadBlocker blocker;
@@ -70,8 +72,11 @@ static ThreadSnapshot of(Thread thread) {
7072
snapshot.locks = EMPTY_LOCKS;
7173
}
7274
if (snapshot.blockerObject != null) {
73-
snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject);
75+
snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal,
76+
snapshot.blockerObject,
77+
snapshot.parkBlockerOwner);
7478
snapshot.blockerObject = null; // release
79+
snapshot.parkBlockerOwner = null;
7580
}
7681
return snapshot;
7782
}
@@ -104,6 +109,13 @@ Object parkBlocker() {
104109
return getBlocker(BlockerLockType.PARK_BLOCKER);
105110
}
106111

112+
/**
113+
* Returns the owner of the parkBlocker if the parkBlocker is an AbstractOwnableSynchronizer.
114+
*/
115+
Thread parkBlockerOwner() {
116+
return (blocker != null && blocker.type == BlockerLockType.PARK_BLOCKER) ? blocker.owner : null;
117+
}
118+
107119
/**
108120
* Returns the object that the thread is blocked on.
109121
* @throws IllegalStateException if not in the blocked state
@@ -211,11 +223,11 @@ Object lockObject() {
211223
}
212224
}
213225

214-
private record ThreadBlocker(BlockerLockType type, Object obj) {
226+
private record ThreadBlocker(BlockerLockType type, Object obj, Thread owner) {
215227
private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache
216228

217-
ThreadBlocker(int typeOrdinal, Object obj) {
218-
this(lockTypeValues[typeOrdinal], obj);
229+
ThreadBlocker(int typeOrdinal, Object obj, Thread owner) {
230+
this(lockTypeValues[typeOrdinal], obj, owner);
219231
}
220232
}
221233

src/jdk.management/share/classes/com/sun/management/doc-files/threadDump.schema.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@
7878
"description": "The blocker object responsible for the thread parking."
7979
}
8080
},
81+
"owner": {
82+
"type": "string",
83+
"description": "The thread identifier of the owner when the parkBlocker is an AbstractOwnableSynchronizer."
84+
}
8185
"required": [
8286
"object"
8387
]
@@ -115,9 +119,9 @@
115119
"items": {
116120
"type": [
117121
"string",
118-
null
122+
"null"
119123
],
120-
"description": "The object for which the monitor is owned by the thread, null if eliminated"
124+
"description": "The object for which the monitor is owned by the thread, null if eliminated."
121125
}
122126
}
123127
},

test/jdk/com/sun/management/HotSpotDiagnosticMXBean/DumpThreads.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
/*
2525
* @test
26-
* @bug 8284161 8287008 8309406 8356870
26+
* @bug 8284161 8287008 8309406 8356870 8365057
2727
* @summary Basic test for com.sun.management.HotSpotDiagnosticMXBean.dumpThreads
2828
* @requires vm.continuations
2929
* @modules java.base/jdk.internal.vm jdk.management
@@ -425,7 +425,9 @@ void testParkedThread(ThreadFactory factory, boolean pinned) throws Exception {
425425
ThreadFields fields = findThread(tid, lines);
426426
assertNotNull(fields, "thread not found");
427427
assertEquals("WAITING", fields.state());
428-
assertTrue(contains(lines, "- parking to wait for <java.util.concurrent.locks.ReentrantLock"));
428+
String line = find(lines, "- parking to wait for <java.util.concurrent.locks.ReentrantLock");
429+
assertNotNull(line, "parking to wait line not found");
430+
assertTrue(line.endsWith(" owner #" + Thread.currentThread().threadId()));
429431

430432
// thread dump in JSON format should include thread in root container
431433
ThreadDump threadDump = dumpThreadsToJson();
@@ -440,6 +442,12 @@ void testParkedThread(ThreadFactory factory, boolean pinned) throws Exception {
440442
String parkBlocker = ti.parkBlocker();
441443
assertNotNull(parkBlocker);
442444
assertTrue(parkBlocker.contains("java.util.concurrent.locks.ReentrantLock"));
445+
446+
// the owner of the parkBlocker should be the current thread
447+
long ownerTid = ti.parkBlockerOwner().orElse(-1L);
448+
assertNotEquals(-1L, ownerTid, "parkBlockerOwner not found");
449+
assertEquals(Thread.currentThread().threadId(), ownerTid);
450+
443451
if (pinned) {
444452
long carrierTid = ti.carrier().orElse(-1L);
445453
assertNotEquals(-1L, carrierTid, "carrier not found");
@@ -684,6 +692,16 @@ private boolean contains(List<String> lines, String text) {
684692
.anyMatch(l -> l.contains(text));
685693
}
686694

695+
/**
696+
* Finds the line of a plain text thread dump containing the given text.
697+
*/
698+
private String find(List<String> lines, String text) {
699+
return lines.stream().map(String::trim)
700+
.filter(l -> l.contains(text))
701+
.findAny()
702+
.orElse(null);
703+
}
704+
687705
/**
688706
* Dump threads to a file in plain text format, return the lines in the file.
689707
*/

test/lib/jdk/test/lib/threaddump/ThreadDump.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,16 @@ public String parkBlocker() {
296296
return getStringProperty("parkBlocker", "object");
297297
}
298298

299+
/**
300+
* Returns the owner of the parkBlocker if the parkBlocker is an AbstractOwnableSynchronizer.
301+
*/
302+
public OptionalLong parkBlockerOwner() {
303+
String owner = getStringProperty("parkBlocker", "owner");
304+
return (owner != null)
305+
? OptionalLong.of(Long.parseLong(owner))
306+
: OptionalLong.empty();
307+
}
308+
299309
/**
300310
* Returns the object that the thread is blocked entering its monitor.
301311
*/

0 commit comments

Comments
 (0)