Skip to content

CONCEPT Cited by 1 source

Fork-join pool

The JVM's ForkJoinPool is a work-stealing thread pool originally introduced in Java 7 for divide-and-conquer parallelism. In Java 21, it is repurposed as the default scheduler for virtual threads — the worker threads of the pool are the carrier threads onto which VTs mount.

VT scheduler pool specifics

Per the JVM-internal VirtualThread default scheduler:

  • Pool size: Runtime.getRuntime().availableProcessors() — equal to the JVM-visible vCPU count.
  • Each carrier is a platform OS thread managed by the fork-join work-stealing machinery.
  • Work is VT tasks: when a VT unmounts, its task is returned to the fork-join queue; when a carrier is idle, it steals work from other queues.

Why the default size is load-bearing

The 4-vCPU default carrier count is the architectural number that makes pinning catastrophic at low scale:

"Because the app is deployed on an instance with 4 vCPUs, the fork-join pool that underpins VT execution also contains 4 OS threads. Now that we have exhausted all of them, no other virtual thread can make any progress." (Source: sources/2024-07-29-netflix-java-21-virtual-threads-dude-wheres-my-lock)

Just four simultaneously pinned VTs exhaust 100% of the pool on such a host. Bigger instances (larger vCPU count) raise the threshold linearly but don't change the class.

Configuration

The default scheduler is not exposed as a public API. You cannot directly resize, swap, or introspect it through standard Java APIs in Java 21.

The system property -Djdk.virtualThreadScheduler.parallelism=<N> tunes the carrier count, but changing it is rarely the right response to pinning — it papers over the underlying structural issue.

Seen in

Last updated · 319 distilled / 1,201 read