The M:N Scheduling Model
Java implements virtual threads using an M:N model:
- M (Millions): A large number of Virtual Threads.
- N (Few): A small number of OS Carrier Threads (usually equal to CPU cores).
When a virtual thread runs code, it is "mounted" onto a carrier thread. When it performs a blocking I/O operation (like reading from a socket), the JVM "unmounts" it, freeing the carrier thread to execute another virtual thread. This non-blocking behavior is transparent to the developer.
Advertisement
Visualization: Carrier Scheduler
Carrier Scheduler Visualization (ForkJoinPool)
Ready Virtual Threads
VT #105
VT #106
VT #107
→ Mount →
Carrier Threads (OS)
Carrier-0
Running: VT #101
Carrier-1
Running: VT #104
Status: Carrier threads execute Virtual Threads from the shared work-stealing queue. When a VT blocks, it is unmounted, and the Carrier picks the next ready VT.
Advertisement
Why This Matters
This model solves the "Thread-per-Request" scalability bottleneck. In traditional server applications, the number of concurrent requests is limited by the number of OS threads (typically a few thousand). With virtual threads, the limit becomes available heap memory, allowing for significantly higher throughput for I/O-bound workloads.