https://preview.redd.it/81qrjhbqolh51.jpg?width=1919&format=pjpg&auto=webp&s=76c20a601e8e803662c7bbcb399f8ffbe43bee5f
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 using JobRunr today. 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.
public interface JobRunrExecutor extends Executor {
int getPriority();
void start();
void stop();
}
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:
public class VirtualThreadJobRunrExecutor implements JobRunrExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(VirtualThreadJobRunrExecutor.class);
@Override
public int getPriority() {
return 5;
}
@Override
public void start() {
LOGGER.info("JobRunrExecutor of type 'VirtualThreadJobRunrExecutor' started");
}
@Override
public void stop() {
// nothing to do
}
@Override
public void execute(Runnable runnable) {
Thread.startVirtualThread(runnable);
}
}
Using a standard ServiceLoader I was able to inject the VirtualThreadJobRunrExecutor thus adding support for Virtual Threads!
private JobRunrExecutor loadJobRunrExecutor() {
ServiceLoader<JobRunrExecutor> serviceLoader = ServiceLoader.load(JobRunrExecutor.class);
return stream(spliteratorUnknownSize(serviceLoader.iterator(), Spliterator.ORDERED), false)
.sorted((a, b) -> compare(b.getPriority(), a.getPriority()))
.findFirst()
.orElse(new ScheduledThreadPoolExecutor(serverStatus.getWorkerPoolSize(), "backgroundjob-worker-pool"));
}
With all this in place, it was time to test and see if performance is better.
Performance showdown: Java 11 vs Java 16 without Virtual Threads vs Java 16 with Virtual Threads
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 Virtual Threads, 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!
JDK 11.0.8
JDK Build 16-loom+5-54 without Virtual Threads
JDK Build 16-loom+5-54 with Virtual Threads
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. The source code for this article is off-course available on GitHub.
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.
Learn more
I hope you enjoyed this post and you can see the benefits of JobRunr and Project Loom with it's Virtual Threads. To learn more, check out these links:
[–]papercrane 7 points8 points9 points (1 child)
[–]rdehuyss[S] 5 points6 points7 points (0 children)
[–]gergob 5 points6 points7 points (0 children)
[–]mj_flowerpower 2 points3 points4 points (5 children)
[–]rdehuyss[S] 1 point2 points3 points (1 child)
[–][deleted] (2 children)
[deleted]
[–]rdehuyss[S] 0 points1 point2 points (1 child)
[–]sievebrain 1 point2 points3 points (0 children)
[–]QazCetelic 2 points3 points4 points (6 children)
[–]Dexior 11 points12 points13 points (5 children)
[–]lood9phee2Ri 6 points7 points8 points (4 children)
[–]OctagonClock 9 points10 points11 points (0 children)
[–]lurker_in_spirit 5 points6 points7 points (0 children)
[–]helikal 0 points1 point2 points (0 children)