This project provides an integration between Java Virtual Threads (Project Loom) and Netty's event loop, enabling low-overhead execution of blocking or CPU-bound work started from the Netty event loop without the usual extra thread hand-offs.
At a high level:
- Each Netty event loop is backed by a virtual thread (the "event loop virtual thread").
- Each such virtual thread is executed on a dedicated carrier platform thread.
- Virtual threads created from the event-loop-specific ThreadFactory returned by the group will be associated with the same EventLoopScheduler and, when possible, will run on the same carrier platform thread as the event loop.
This allows code that must block (for example, blocking I/O or synchronous library calls) to be executed without moving work between unrelated threads, reducing wake-ups and improving cache locality compared to offloading to an external thread pool.
- Create a
VirtualMultithreadIoEventLoopGrouplike any other NettyEventLoopGroup. It behaves like aMultiThreadIoEventLoopGroupfrom the Netty API, but the event loops are driven by virtual threads and coordinated with carrier platform threads by a per-event-loopEventLoopScheduler. - Call
group.vThreadFactory()to obtain aThreadFactorythat creates virtual threads tied to anEventLoopSchedulerof the group. When those virtual threads block, the scheduler parks them and resumes them later using the carrier thread. - Virtual threads created via the group's
vThreadFactory()will attempt to inherit the scheduler and run with low-overhead handoffs back to the event loop when continuing work. - Virtual threads created with
Thread.ofVirtual().factory()(the JVM default factory) do NOT automatically inherit the group's scheduler. - The library requires a custom global virtual thread scheduler implementation to be installed: set
-Djdk.virtualThreadScheduler.implClass=io.netty.loom.NettyScheduler(or use the convenience helpers in the code/tests that set up the scheduler). - Blocking I/O support that relies on per-carrier pollers currently depends on the JVM poller mode (the code checks
jdk.pollerMode==3). See notes below.
- You have code that must perform blocking operations from a Netty handler and you want to avoid an extra thread hand-off.
- You want fewer CPU wake-ups and better cache locality by keeping related work on the same carrier platform thread.
Caveats:
- This project leverages experimental JVM features (Project Loom) and assumes recent Java versions (Java 21+ recommended).
- You must install the Netty-specific scheduler (see above) for
VirtualMultithreadIoEventLoopGroupto be usable.
See the runnable example and step-by-step instructions in the example module:
(That README contains the minimal build and run commands, including how to start the example server and run a quick curl smoke test.)
This work targets Project Loom features and was developed and tested against very recent OpenJDK / Loom builds. If you don't want to build OpenJDK yourself, a convenient set of prebuilt Loom-enabled JDK images is available from Shipilev's builds:
Follow that site to download a suitable JDK image for your platform and point JAVA_HOME to the JDK image before running the project. Example:
export JAVA_HOME=/path/to/loom/build/linux-x86_64-server-release/jdk/If you prefer to build from the OpenJDK loom repository yourself, the upstream source is:
If you have a local build of the latest Loom-enabled OpenJDK, point JAVA_HOME to that build before running tests and benchmarks. Example:
export JAVA_HOME=/path/to/your/loom/build/linux-x86_64-server-release/jdk/
mvn clean install-
Install the Netty scheduler as the JVM virtual-thread scheduler with:
-Djdk.virtualThreadScheduler.implClass=io.netty.loom.NettyScheduler -
Some blocking I/O integrations rely on per-carrier pollers. The code checks
jdk.pollerModeand expects value3for per-carrier pollers. This can be controlled via JVM flags or defaults depending on your JVM version. -
Use
group.vThreadFactory()from inside Netty handlers if you want the spawned virtual thread to be associated with the same event loop scheduler as the handler's event loop. -
If you need the virtual thread to inherit the scheduler when forking tasks via StructuredTaskScope, pass the group's
vThreadFactory()to the scope'swithThreadFactory(...).
This project uses Maven for build and dependency management.
- Build the project:
mvn clean install- Run the benchmarks (optional):
cd benchmarks
mvn clean install
java -jar target/benchmarks.jar- Maven 3.6+
- To use the
VirtualMultithreadIoEventLoopGroupset the JVM property to install the Netty scheduler: -Djdk.virtualThreadScheduler.implClass=io.netty.loom.NettyScheduler
- Project Loom (OpenJDK): https://openjdk.org/projects/loom/
- Netty Project: https://netty.io/
For more details, see the source code and benchmark results in the respective modules.