Pinning Pitfalls: When Virtual Threads Get Stuck

Published on February 14, 2026

The Promise of Virtual Threads

Virtual threads (Project Loom) promised to liberate us from the constraints of OS threads. By mapping many virtual threads to a few carrier threads, we can achieve high throughput for IO-bound workloads. However, there is a catch: Pinning.

What is Pinning?

A virtual thread is said to be "pinned" to its carrier thread when it executes a synchronized block or method, or when it performs a native call via JNI. During this time, the virtual thread cannot be unmounted from the carrier thread, even if it performs a blocking operation.

If a pinned virtual thread blocks (e.g., waiting for a lock or IO), it holds onto the carrier thread, preventing it from serving other virtual threads. This effectively reduces the parallelism of your application and can lead to starvation if too many carrier threads are pinned simultaneously.

Visualizing the "Sticky Gear" Effect

Imagine the scheduler as a system of gears driving tasks. A pinned thread is like a sticky gear that refuses to rotate smoothly or disengage, causing friction in the system.

Figure 1: Simulation of a carrier thread (gear) getting "stuck" due to pinning.

Avoiding the Pitfall