JDK 16 early access has a build available including Project Loom which is all about virtual, light-weight threads (also called Fibers) that can be created in large quantities, without worrying about exhausting system resources.
Project Loom is also the reason why I did not use a reactive framework for JobRunr as it will change the way we will write concurrent programs. Project Loom with it’s Virtual Threads is supposed to be a drop-in replacement for the existing threading framework and I tried it out today using JobRunr. This also means that JobRunr, as of v0.9.16 (to be released soon), will support project Loom out-of-the-box while still also supporting every JVM since Java 8!
Implementing support for Project Loom was easier than I thought using a ServiceLoader
. I extracted a simple interface called JobRunrExecutor
from the existing ScheduledThreadPool
.
I then created another implementation of the interface using JDK 16 making use of Project Loom which does nothing more than delegating to a Virtual Thread:
Using a standard ServiceLoader
I was then able to inject the VirtualThreadJobRunrExecutor
thus adding support for Virtual Threads!
With all this in place, it was time to test and see if performance is better.
Performance showdown: Java 11 vs Java 16 without Project Loom vs Java 16 with Project Loom
As I want to make sure performance is as good as it gets, I have some end-to-end tests which I run regularly, which can be found in the following GitHub repository: https://github.com/jobrunr/example-salary-slip. In that project, paychecks are generated for 2000 employees using a Word template and then transformed to PDF.
To compare Java 11, Java 16 and Java 16 with Project Loom, I ran this project again and hooked up JVisualVM. To give the JVM some time to warm up, I ran each test 3 times.
Comparing performances is not fair as JobRunr only checks for new jobs every 15 seconds and thus comparing these numbers just depends on the fact when I enqueued the jobs. Just to be complete, you can find the numbers below:
Run | Java 11 | Java 16 | Java 16 with Loom |
---|---|---|---|
1 | 140 | 146 | 151 |
2 | 132 | 167 | 139 |
3 | 139 | 167 | 137 |
All numbers are in seconds. |
What we can compare is the results from JVisualVM. And boy, are these worthwhile!
The biggest difference is memory usage:
- JDK 11 occupied a heap of 6.8 GB with a peak use of 4.7 GB
- JDK 16 without Virtual Threads occupied a heap of 4.6 GB with a peak use of 3.7 GB
- JDK 16 with Virtual Threads occupied a heap of 2.7 GB with a peak use of 2.37 GB
So, using JDK 16 with these light-weight virtual threads resulted in:
- only 50% usage of heap memory compared to JDK 11
- only 64% usage of heap memory compared to JDK 16 without virtual threads
Conclusion
While I initially thought that Project Loom would increase performance a lot, I currently see major improvements in memory usage. I was surprised as how easy it was to support Project Loom thanks to the use of the ServiceLoader.
Do note that JDK 16 is an early-access build and it’s not even sure if Project Loom will be part of JDK 16.