{{announcement.body}}
{{announcement.title}}

JobRunr, Project Loom, and Virtual Threads

DZone 's Guide to

JobRunr, Project Loom, and Virtual Threads

After integrating the Virtual Threads of Project Loom, it's time for a showdown between Java 11, Java 16 without Virtual Threads, and Java 16 with Virtual Threads.

· Java Zone ·
Free Resource

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. It's Virtual Threads are supposed to be a drop-in replacement for the existing threading framework and I tried it out with 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.

Java
 




x


 
1
public interface JobRunrExecutor extends Executor {
2
 
           
3
    Integer getPriority();
4
 
           
5
    void start();
6
 
           
7
    void stop();
8
 
           
9
}



The JobRunrExecutor interface which is implemented by 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:

Java
 




xxxxxxxxxx
1
24


 
1
public class VirtualThreadJobRunrExecutor implements JobRunrExecutor {
2
 
          
3
    private static final Logger LOGGER = LoggerFactory.getLogger(VirtualThreadJobRunrExecutor.class);
4
 
          
5
    @Override
6
    public Integer getPriority() {
7
        return 5;
8
    }
9
 
          
10
    @Override
11
    public void start() {
12
        LOGGER.info("JobRunrExecutor of type 'VirtualThreadJobRunrExecutor' started");
13
    }
14
 
          
15
    @Override
16
    public void stop() {
17
        // nothing to do
18
    }
19
 
          
20
    @Override
21
    public void execute(Runnable runnable) {
22
        Thread.startVirtualThread(runnable);
23
    }
24
}



Using a standard ServiceLoader, I was able to inject the VirtualThreadJobRunrExecutor thus adding support for Virtual Threads!

Java
 




xxxxxxxxxx
1


 
1
private JobRunrExecutor loadJobRunrExecutor() {
2
    ServiceLoader<JobRunrExecutor> serviceLoader = ServiceLoader.load(JobRunrExecutor.class);
3
    return stream(spliteratorUnknownSize(serviceLoader.iterator(), Spliterator.ORDERED), false)
4
            .sorted((a, b) -> b.getPriority().compareTo(a.getPriority()))
5
            .findFirst()
6
            .orElse(new ScheduledThreadPoolExecutor(serverStatus.getWorkerPoolSize(), "backgroundjob-worker-pool"));
7
}



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 this 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!

JVisualVM 11.08

JDK 11.0.8

JVisualVM 16 without virtual threads
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 also 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.

The source code for this article is off-course available on GitHub.

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 guides:

Topics:
java, jonrunr, loom, virtual threads

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}