JDK-8365057 : Add support for java.util.concurrent lock information to Thread.dump_to_file
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: svc
  • Priority: P2
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2025-08-07
  • Updated: 2025-08-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 26
26Unresolved
Related Reports
Relates :  
Description
Thread.dump_to_file supports (as of JDK-8356870) dumping of intrinsic lock (monitor enter/exit) information, but java.util.concurrent (j.u.c) lock information is not included. A way to collect j.u.c lock information and include it in the dump should be provided.

Concerns have been raised about the additional overhead collecting this information may add. The latency implications of requiring a STW pause and iterating the Java heap mean that inclusion of j.u.c lock information should be an opt-in feature.

One possible solution would be to add a “-l” option to Thread.dump_to_file. This is analogous to how Thread.print behaves today:
===
$ jcmd Sleeper help Thread.print
130527:
Thread.print
Print all threads with stacktraces.

Impact: Medium: Depends on the number of threads.

Syntax : Thread.print [options]

Options: (options must be specified using the <key> or <key>=<value> syntax)
	-l : [optional] print java.util.concurrent locks (BOOLEAN, false)
	-e : [optional] print extended thread information (BOOLEAN, false)
===

This way, only users who are willing to accept a (possibly significant) latency impact can choose to opt-in. For the default behavior (no j.u.c lock information), there should be no noticeable performance regression. As j.u.c lock information is often needed to troubleshoot completely hung applications, even a large pause will be acceptable for many scenarios.

Without this information, it is very difficult to identify the root cause of hangs where a VT acquires a j.u.c lock and then unmounts from its carrier thread. Such “lost lock” situations have proven to be very  problematic in the past and motivated the addition of j.u.c lock information to platform thread dumps (e.g. Thread.print) during the JDK 5 era (see JDK-5086470). The same motivations / tradeoffs apply here.

Comments
So the value could be stale (though extremely unlikely in practice), but it can change as soon as it is read even if volatile, so it makes no difference AFAICS.
29-08-2025

Looks like I forgot what was the issue with AOS owners. The problem is AOS.exclusiveOwnerThread field is not volatile as I thought and we cannot be sure we got actual value. But I suppose it's still safe to use the value (i.e. oop is valid). I looked at "Thread.print" command implementation - it just reads the value with obj_field(), gets its JavaThread, etc. It's executed at safepoint (on VMThread), but I don't see how it can help here.
29-08-2025

Again, the issue is that exclusiveOwnerThread can only be used by the lock owner. The only thing that other threads can do is test if the value is equal to the current thread. I've put one of the prototypes into a branch with the change to use a releasing store. We are not proposing this for main line, it was only an experiment to get some sense of the impact to lock/unlock operations. https://github.com/openjdk/jdk/compare/master...AlanBateman:jdk:aqs-owner
28-08-2025

> Do I understand correctly that you think it's safe to read AbstractOwnableSynchronizer.exclusiveOwnerThread field, wrap it to OopHandle and return to Java? (just need to ensure the value is read atomically) Yes, perfectly safe.
28-08-2025

This bit I don't follow. You are doing a dump and thread T1 is blocked on a synchronizer object (AOS subclass) that is owned by thread T2, so you have the java.lang.Thread oop for T2. > Where does the `JavaThread` come into it? In your previous comment: >> But it is not unsafe in a general sense when T2 is a Java object (it is unsafe at the VM level as we would need to ensure the JavaThread` cannot terminate). We don't need JavaThread. We need j.l.Thread object only. We don't care if the thread terminates or releases the lock soon. Thread.dump_to_file does not guarantee consistency between threads, it guarantees only consistency of each thread snapshot (i.e. when we make the snapshot, the thread was blocked and parkBlocker was owned by the reported owner thread). Do I understand correctly that you think it's safe to read AbstractOwnableSynchronizer.exclusiveOwnerThread field, wrap it to OopHandle and return to Java? (just need to ensure the value is read atomically)
28-08-2025

> But we have naked thread oop between reading and handling and this is another thread. So theoretically JavaThread of this other thread may terminate, and it's threadObj can be destroyed. This bit I don't follow. You are doing a dump and thread T1 is blocked on a synchronizer object (AOS subclass) that is owned by thread T2, so you have the java.lang.Thread oop for T2. Where does the `JavaThread` come into it? If you wanted to do anything with the JavaThread you would have to use the ThreadSMR functions to get the JavaThread ptr, handshake with itr then check if it still owns the AOS. If you are not using STW safepoint then you cannot guarantee a consistent snapshot across threads.
28-08-2025

[~dholmes] Well, we want to get the owner thread in the VM (in the handshake, when we found park blocker) to get consistent snapshot. So we have AbstractOwnableSynchronizer object. It has "volatile Thread exclusiveOwnerThread" field. Ok, we read the value and handle it. That's all we need. But we have naked thread oop between reading and handling and this is another thread. So theoretically JavaThread of this other thread may terminate, and it's threadObj can be destroyed. To be sure we are safe we need to ensure JavaThread of this object is in our TLH. But to check it we need to read java_lang_Thread::thread() and we are not sure it's safe (we trying to check the safety).
27-08-2025

I misunderstood what was being asked, but this is no different to what is done in JDK-8356870. What is the safety concern here? If the owner is currently listed as thread T2 then that could change at any instant so the info is of very limited value. But it is not unsafe in a general sense when T2 is a Java object (it is unsafe at the VM level as we would need to ensure the JavaThread` cannot terminate). What am I missing here?
26-08-2025

As Alan explained, initially it was planned to report owner of the parkBlocker, but the owner is a different (not target) thread, so we cannot to it safely. But the j.u.c lock info is very important to analyze VM in "problematic" state, so the request is to introduce an option to gather the info. To get the info we have to scan heap at safepoint and create list/map of all AbstractOwnableSynchronizer and their owner threads. Then this map is used when thread snapshots are created (as it is now - in handshakes). We can add info about all j.u.c lock for the thread or add "ownerThread" or "ownerThreadID" field to "parkBlocker". The approach has risk to get inconsistent data in the thread dump (lock owner can change while we iterate all threads), but in case of deadlock this should provide enough information for analysis. I think it's possible to do some checks (at least if the same thread is still owns AbstractOwnableSynchronizer) and exclude obsolete data from report (I think it makes sense to distinguish cases when there is no owner and we cannot provide information about it).
26-08-2025

The scenario is target thread T1 and owner thread T2. The handshake with T1 takes a snapshot of T1. T1's parkBlocker is an AQS when it is parked trying to acquire a lock. It would be great if we could read the AQS exclusiveOwnerThread field in the handshake with T1 to see that the current owner is T2 but we can't do that safely. The only thread that can safely read exclusiveOwnerThread is the owner to check equality with the current thread.
26-08-2025

But if we are handshaking a target thread T, and we want to print the synchronizers owned by T, then seeing an owner is T has to be safe because it is held by the handshake. In a sense the handshaker thread acts like a proxy for T during the handshake. In memory model terms all writes by T prior to the handshake are visible to the handshaker.
26-08-2025

This thread dump uses handshakes rather than a VM operation, so no STW at this time.
25-08-2025

I don't see how reading asynchronously is required. Reading any fields associated with a JavaThread or data from the Java heap should be safe to do if all JavaThreads are safepointed as part of a STW pause.
25-08-2025

Just to add some elaboration—the reason the exclusive owner "works" is because a thread can see its own writes—so if the value written (using plain write semantics) is exactly the owning thread then it is the owner—all other values indicate that the owner is some other thread (or no thread). This means that it cannot be safely read by a third-party thread to determine which thread is the current owner.
25-08-2025

Right now, the parkBlocker is included. If a virtual thread is blocked on a ReentrantLock then you'll see output like this in the dump: ``` { "tid": "39", "time": "2025-08-21T15:39:15.194851Z", "virtual": true, "name": "foo", "state": "WAITING", "parkBlocker": { "object": "java.util.concurrent.locks.ReentrantLock$NonfairSync@1a6c5a9e" }, "stack": [ : ] } ``` The intention was for the parkPark object to have an additional property for the exclusiveOwnerThread when the parkBlocker is an AbstractOwnableSynchronizer. We had to pull this out because it's not safe to read this asynchronously (setExclusiveOwnerThread uses plain access. There are trade-off with all solutions looked at so far.
21-08-2025

> How will you track what locks (really AbstractOwnableSynchronizers) are owned by virtual threads? I believe the suggestion is to do what Thread.print does when the -l option is specified, which is to walk the entire heap to locate all AbstractOwnableSynchronizer instances, and then display the vthread (or platform thread) that owns them.
08-08-2025

How will you track what locks (really AbstractOwnableSynchronizers) are owned by virtual threads?
08-08-2025