An investigation into whether pooling Java virtual threads (VTs) improves performance, inspired by Quarkus's work on Loom integration. Benchmarks show pooling gives a 13.5% throughput gain on a 4 GB heap, but collapses catastrophically on a 1 GB heap — 461 Full GCs and 30+ seconds of GC pauses in a 60-second run. The culprit is StackChunk behavior: when long-lived pooled VTs have their StackChunks promoted to old gen, the JVM allocates fresh young-gen chunks and abandons the old ones, creating severe allocation churn. An attempted fix to reuse old-gen chunks made things worse — the chunks were 4x oversized because they were allocated during JIT warmup (C1-compiled, larger stack frames) before C2 optimization shrank the frozen stack size. The oversized chunks pinned in old gen left the GC unable to reclaim enough heap. The conclusion: the official advice to never pool virtual threads is correct, and the reason is deeper than 'they're cheap to create' — it's about JIT compilation timing and StackChunk lifecycle interactions with generational GC.
30.2K Impressions1 Comment