Building my own runtime taught me how modern event-driven systems actually work under the hood. Here are the five biggest lessons I learned about CPU usage, kernel APIs, and performance benchmarking.
Building zThread taught me far more about Linux and the JVM than I expected.
When I started this project, my goal wasn't to replace Netty or Project Reactor. I wanted to understand how modern event-driven systems actually work under the hood. (This same curiosity eventually led to building ReactiveChainDB v2 to bypass CPU limitations entirely.)
After months of reading Linux documentation, JVM internals, NGINX, Netty, and implementing my own runtime, here are the five biggest lessons.
1. "Zero CPU" doesn't exist
When I first learned about NGINX and epoll, I kept hearing:
"NGINX uses almost no CPU while waiting."
I interpreted that as zero CPU.
That's not actually true.
What's really happening is:
Application
↓
epoll_wait()
↓
Kernel blocks thread
↓
No CPU consumed while sleeping
↓
Kernel wakes thread only when an event occurs
The application still uses CPU while processing events.
The real optimization is:
Don't waste CPU while waiting.
That changed how I thought about concurrency.
2. The kernel already solves waiting
Before this project, I assumed event loops were mostly implemented in Java.
They're not.
Linux already provides mechanisms for nearly every kind of asynchronous waiting:
- epoll
- eventfd
- timerfd
- signalfd
- inotify
Instead of writing complicated polling loops, applications can ask the kernel:
"Wake me when something interesting happens."
That realization became the foundation of zThread.
3. Throughput benchmarks can be misleading
One of my earliest benchmarks compared:
ArrayBlockingQueue.offer()eventfd_write()
The results looked terrible.
Then I realized I wasn't measuring equivalent work.
A queue insertion is an in-memory operation.
An eventfd_write() crosses into the kernel and wakes an event loop.
Those aren't comparable.
I completely redesigned the benchmark suite to compare end-to-end event processing pipelines instead of isolated API calls.
That was probably the most valuable lesson I learned about benchmarking.
4. Foreign Function & Memory API changes everything
A few years ago, calling Linux APIs from Java usually meant JNI.
Today, Java's Foreign Function & Memory (FFM) API makes native interoperability much more approachable.
For zThread, that meant I could call Linux APIs like:
epoll_create1epoll_waiteventfdtimerfd_create
directly from Java with a much cleaner integration model.
It's one of the most exciting additions to the modern JVM ecosystem.
5. Building the runtime was easier than building good benchmarks
Ironically, writing the event loop wasn't the hardest part.
Designing benchmarks that were:
- fair
- reproducible
- statistically meaningful
- comparable across frameworks
turned out to be significantly more challenging.
It's surprisingly easy to publish impressive-looking numbers.
It's much harder to build benchmarks that other engineers trust.
That experience fundamentally changed how I read performance claims online.
What's next for zThread?
The project is now:
- Open source
- Available on Maven Central
- Focused on Linux event-driven programming in Java
My next goals are:
- Better benchmarking
- Reactor integration
- More examples
- Better documentation
- Community feedback
Final thoughts
This project started as a curiosity:
"How does NGINX actually wait without wasting CPU?"
That single question led me down a rabbit hole of Linux kernel APIs, JVM internals, concurrency, benchmarking, and systems programming.
Whether zThread becomes widely adopted or simply remains a learning project, it has already achieved its biggest goal:
It taught me how modern event-driven systems really work.
If you've worked on Netty, Reactor, Vert.x, or JVM performance engineering, I'd love to hear your thoughts or feedback.
Project link:https://github.com/namanONcode/zThread



Top comments (0)