Virtual Threads: Mounting and Unmounting
A virtual thread runs by mounting onto a carrier thread, typically a platform thread, and may unmount when it reaches a blocking operation that the runtime can suspend. This allows the carrier to execute other virtual threads instead of remaining occupied by the blocked task.
A virtual thread is not permanently tied to one platform thread. When the virtual thread is scheduled to run, the JVM mounts it onto a carrier thread, which supplies the underlying OS-thread execution resource.
If the virtual thread performs an operation that can suspend it, the JVM can save its execution state and unmount it from the carrier. The carrier then becomes available to run another virtual thread. This is a key part of how virtual threads support large numbers of concurrent tasks without requiring one platform thread per task.
Mounting and unmounting are runtime scheduling details rather than operations application code normally performs explicitly. The application generally creates and uses virtual threads through APIs such as `Thread.startVirtualThread(...)` or an executor designed for virtual threads.
Not every operation necessarily permits efficient unmounting. In particular, code that pins a virtual thread to its carrier can prevent the carrier from being freed during a blocking period. Understanding pinning is therefore important when diagnosing unexpectedly poor virtual-thread scalability.
Key points
- A virtual thread executes on a carrier thread while it is mounted.
- A virtual thread can be unmounted when it is suspended by a suitable blocking operation.
- After unmounting, the carrier can execute another virtual thread.
- Mounting and unmounting are handled by the JVM rather than directly by application code.
- Pinned virtual threads can remain associated with their carriers during blocking operations.
Interview questions
Why does unmounting improve virtual-thread scalability?
If a virtual thread waits during a blocking operation that supports suspension, it does not need to occupy a carrier for the entire wait. The JVM can suspend the virtual thread and make the carrier available for another runnable virtual thread. This separates the number of concurrent tasks from the number of platform threads needed to execute them.
Does a virtual thread always have the same carrier thread?
No. A virtual thread is not permanently bound to a particular carrier. When it becomes runnable again, the JVM scheduler can execute it on an available carrier. This flexibility is important because virtual threads are designed to be much more numerous than platform threads.
What does it mean for a virtual thread to mount onto a carrier thread?
Mounting means the JVM begins executing a virtual thread's continuation on a carrier thread. The carrier provides the actual platform-thread execution resource. The virtual thread can later be suspended and unmounted, allowing that carrier to run other work. Application code normally does not control this process directly.
What is virtual-thread pinning, and why does it matter?
Pinning occurs when a virtual thread cannot unmount from its carrier during a blocking period. A notable case is blocking while executing inside a `synchronized` region or certain native/foreign operations, depending on the JDK implementation and operation involved. Pinning can reduce the effective scalability advantage because the carrier remains occupied. Applications with substantial pinning may need profiling and redesign of the relevant blocking paths.
How would you explain mounting and unmounting during a database call?
Suppose a virtual thread issues a database operation that causes it to wait. If the relevant blocking path permits suspension, the virtual thread can be unmounted while waiting rather than occupying its carrier for the full duration. The carrier can then execute another runnable virtual thread. When the operation becomes ready, the virtual thread becomes runnable and can resume on a carrier selected by the scheduler.