How do you detect virtual-thread pinning in a running Java application?
answer
- -Djdk.tracePinnedThreads=full/short (deprecated)
- JFR jdk.VirtualThreadPinned event + threshold
- Analyze with JMC or jfr print
- Companion: jdk.VirtualThreadSubmitFailed
- Symptom: throughput plateaus, carrier pool saturated
basics
~10 sRun with -Djdk.tracePinnedThreads to print a stack trace whenever a thread blocks while pinned (older JDKs), or record a JFR profile and look for jdk.VirtualThreadPinned events. Both show exactly where the pinning happens.
solid answer
~50 sTwo main tools. On Java 21–23, start the JVM with `-Djdk.tracePinnedThreads=full` (or `short`); it logs a stack trace each time a virtual thread *blocks while pinned*, pointing at the offending `synchronized` or native frame. It's noisy and meant for diagnosis, not production. The modern, low-overhead approach is **JDK Flight Recorder (JFR)**: enable the `jdk.VirtualThreadPinned` event (it has a configurable duration threshold so you only capture meaningful pins), produce a `.jfr` recording, and analyze it in JDK Mission Control or with `jfr print`. JFR is cheap enough to leave on in production and integrates with continuous profiling. Note that `jdk.tracePinnedThreads` is deprecated/removed in newer JDKs, so JFR (plus the `jdk.VirtualThreadSubmitFailed` event for scheduler stress) is the forward-looking answer. You can also infer pinning indirectly from symptoms: rising latency under load with a fully-utilized but small carrier pool.
go deeper
Knows the -Djdk.tracePinnedThreads flag exists and prints where pinning happens.
Can run with tracePinnedThreads and read the stack trace to find the synchronized/native frame at fault.
Leads with JFR (jdk.VirtualThreadPinned with a threshold, analyzed in JMC/jfr print), knows tracePinnedThreads is deprecated, and correlates symptoms (plateaued throughput, saturated carriers).
Designs continuous observability: JFR always-on with pinned-event alerting, dashboards on carrier saturation and submit-failed events, and a playbook tying detection to the synchronized→ReentrantLock remediation across services.
## Why detection needs tooling Pinning is invisible in normal logs: code runs correctly, just *less concurrently* than you expect. A pinned virtual thread holds its carrier OS thread, so the symptom is throughput that plateaus far below what the number of virtual threads should allow. To find *where* it happens you need the runtime to tell you, which is what these tools do. ## Tool 1: `-Djdk.tracePinnedThreads` (system property / JVM flag) Added with virtual threads, this flag makes the JVM print a stack trace **whenever a virtual thread blocks while pinned**. Two modes: - `=short` — a one-line-per-frame summary highlighting the frame that caused the pin. - `=full` — the complete stack trace, with the pinning frame marked (e.g., a `synchronized` monitor or a native method). It writes to standard out/err. It is a **diagnostic** tool: under load it can be very noisy and add overhead, so you use it in a test or staging run, not steady-state production. **Important:** this flag is **deprecated and being removed** in newer JDKs (after JEP 491 reduced synchronized pinning), in favor of JFR. So mention it but lead with JFR for modern JDKs. ## Tool 2: JDK Flight Recorder (JFR) — the modern answer **JFR** is a built-in, very-low-overhead event-recording framework in the JVM. It emits structured events you capture into a `.jfr` file. The relevant event is: - **`jdk.VirtualThreadPinned`** — emitted when a virtual thread blocks while pinned, including the stack trace and the duration it was pinned. It has a **threshold** setting (e.g., only record pins longer than 20 ms) so you ignore harmless micro-pins and surface the real ones. - **`jdk.VirtualThreadSubmitFailed`** — emitted when the scheduler fails to submit a virtual thread (a sign of carrier-pool stress), useful as a companion signal. How to use it: 1. Start a recording: `-XX:StartFlightRecording=duration=60s,filename=rec.jfr`, or attach at runtime with `jcmd <pid> JFR.start`. 2. Ensure the pinned event is enabled with a threshold (via a `.jfc` settings file or the `settings` parameter). 3. Analyze: open `rec.jfr` in **JDK Mission Control (JMC)** for a GUI, or run `jfr print --events jdk.VirtualThreadPinned rec.jfr` on the command line to dump events and their stack traces. Because JFR overhead is tiny, you can run it continuously in production and alert on pinned-thread events—this is the recommended observability strategy. ## Tool 3: indirect / symptom-based detection Even without flags you can suspect pinning from behavior: latency climbs under concurrency, the carrier pool (default size = CPU count, configurable via `jdk.virtualThreadScheduler.parallelism`) is saturated, and adding more virtual threads doesn't help. A thread dump (`jstack`/`jcmd Thread.print`) showing virtual threads stuck inside `synchronized` regions or native frames corroborates it. APM/continuous-profiling products increasingly surface the JFR pinned event directly. ## Summary of what a strong answer states - `jdk.tracePinnedThreads=full|short` for ad-hoc diagnosis (and that it's deprecated on newer JDKs). - JFR `jdk.VirtualThreadPinned` (with threshold) + JMC/`jfr print` as the production-grade, low-overhead method, plus `jdk.VirtualThreadSubmitFailed` as a companion. - Symptom-level signs: plateaued throughput, saturated small carrier pool, thread dumps stuck in synchronized/native frames.
- Why prefer JFR over jdk.tracePinnedThreads in production?JFR has near-zero overhead, supports a duration threshold so you only record meaningful pins, produces structured analyzable events, and integrates with JMC and continuous profiling. tracePinnedThreads is noisy, higher-overhead, and deprecated/removed on newer JDKs.
- You see throughput plateau but no obvious deadlock. How do you confirm it's pinning?Take a JFR recording with jdk.VirtualThreadPinned enabled (low threshold) and inspect the stack traces, or grab a thread dump and look for virtual threads stuck inside synchronized blocks or native frames while the small carrier pool is saturated.
saying these in an interview costs you the question
- Recommending jdk.tracePinnedThreads as the only/permanent solution—it's deprecated and noisy
- Saying you must use a third-party agent; JFR is built into the JDK
- Claiming JFR has high overhead—it's specifically designed to be near-zero cost
- Confusing detecting pinning with detecting deadlocks (jstack); pinning needs the pinned-thread event