Skip per-thread event pooling on virtual threads#17
Merged
Conversation
Virtual threads are typically short-lived and can exist in the millions: the per-thread PooledEmitState would be allocated on a vthread's first log call, used a handful of times, and abandoned - worse than allocating a fresh event per emit, and the pool's memory footprint would scale with the number of live virtual threads. emit() now gates the pool claim on Thread.isVirtual(), checked before touching the ThreadLocal so the per-vthread entry is never created; virtual threads fall through to the existing fresh-allocation path. Sources compile at Java 17, where isVirtual() does not exist, so the check is a MethodHandle resolved once at class initialization: the real (intrinsified) method on Java 21+, or a constant-false handle on older JVMs that the JIT folds away. Add Log4j2VirtualThreadTest (vthreads started reflectively, gated on JRE >= 21): platform-thread detection, vthread detection, and an end-to-end logging smoke test from a virtual thread. Platform-thread cost measured with 3-fork JMH runs: 16.65±0.48 -> 16.60±0.15 ops/us, statistically indistinguishable, zero allocation preserved. README documents the caveat next to the zero-allocation claim.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Virtual threads are typically short-lived and can exist in the millions: the per-thread
PooledEmitStatewould be allocated on a vthread's first log call, used a handful of times, and abandoned — strictly worse than allocating a fresh event per emit, and the pool's memory footprint would scale with the number of live virtual threads (a million vthreads × ~500 B of pooled state is real memory).emit()now gates the pool claim onThread.isVirtual(), checked before touching the ThreadLocal so the per-vthread entry is never created; virtual threads fall through to the existing fresh-allocation path (the same one the thread-locals-disabled mode uses).isVirtual()doesn't exist: the check is aMethodHandleresolved once at class init — the real (intrinsified) method on Java 21+, or a constant-falsehandle on older JVMs that the JIT folds away entirely.invokeExacton a static final handle inlines like a direct call.Testing
Log4j2VirtualThreadTest— virtual threads started reflectively (tests also compile at the 17 source level), the vthread tests gated with@EnabledForJreRange(min = JAVA_21)and executing for real on the build JDK: platform-thread detection, vthread detection, and an end-to-end logging smoke test from a virtual thread with context + event attrs intact.slogSimple_enabled16.65 ± 0.48 → 16.60 ± 0.15 ops/μs — statistically indistinguishable, zero-allocation property preserved.checkpasses (all test tasks).