Project Loom - A Preview Feature Of Java 19

Hi readers, in this article, we will go into detail about other features of JDK 19, continuing the previous article. Before moving forward in this, requesting readers to go with the last article on JDK 19 here. This article will review the most significant changes delivered as part of JDK 19, the most recent release of the Java platform. 

This article will be more functional than technical, as it covers how this project looms coming into the picture.

JDK 19 is the second release of 2022 and the 11th release since Java switched to a semi-annual release cadence. JDK 19 will replace JDK 18, and JDK 20 will suppress it in March 2023. The next LTS release is JDK 21, due in September 2023. JDK 19 is the release at no fee terms & conditions so that anyone can use it in production at no cost. 

JDK 19 brings up 7 enhancement proposals. There will be a preview reach release with 6 new features delivering either incubators or preview features. It listed those 7 enhancement proposals below.

Language Features

  1. Pattern Matching for Switch (Preview)
  2. Record Patterns (Preview)

Libraries

  1. Foreign Function and Memory API (Preview)
  2. Vector API (4th Incubator)

Port

  1. Linux/RISC-V Port

Loom Related

  1. Virtual Threads (Preview)
  2. Structured Concurrency (Preview)

Loom - Introduction
 

First Preview of JDK 19

The last two features in our list are the first preview that arrives at the JDK 19 from the project loom. 

Project Loom aims to revamp the Java Concurrency Model to meet the needs of today’s high-end, high-scale server applications. Let’s dive deep into how the project looms in the following parts of the article.

Motivation - Loom - Why needed?
 

Existing threads are great but too heavy 

Why do we need a loom? Even from the first release of Java, the platform has allowed for written concurrent code using a straightforward model. When first introduced, Java threads allow for a platform-independent way to write concurrent code. Without Java, developers who wanted different tasks running on different threads often had to deal with platform-specific code.

  1. Java threads offer many advantages for developers and runtimes 
  2. Readable and sequential code with understandable control flow
  3. Good debugging and serviceability, with comprehensive stack traces
  4. Natural unit scheduling for operating systems.

Developers use Java threads, which Java then converts to operating system threads for each supported operating system. There are a lot of great things about these threads. They offer a natural programming model with readable sequential code using control flow operators that users understand loops, and conditional exceptions, users get great debugging and serviceability, and reliable stack traces and threads are the natural unit of scheduling for the operating system. 

Loom - Advantages

Project Loom is striving to keep all those advantages.

  1. But the platform threads are heavyweight;
  2. Significant metadata (>2KB) and stack space (1MB)
  3. Expensive to create; limited to a few thousand
  4. The operating system implements threads too heavily, which limits the problem. 
  5. If we are using threads to service operations that spend a relatively large amount of time waiting for resources.

For example, if we have server handling requests that come through a network, the limit in the number of threads can severely limit your application to well below the capacity of the hardware or the network. So threads become a contrasting serving factor in servers throughout. 

  • One option to deal with this limit is to turn it into a reactive framework by not representing concurrent operations directly as threads.
  • We can scale well beyond the limits posed by the thread-per-request model but at a huge cost. 
  • It’s much more complicated, and it’s harder to write, it’s harder to read, and it’s much harder to debug or profile because they build the platform in all of its layers and all of its tools around threads. 

Some developers are turning to async/reactive frameworks to scale beyond stack traces.

  • It works... but the programming model is terrible.
  • Hard to read, non-intuitive control flow, hard to debug, incomprehensible stack traces
  • Others are turning to languages like Go, which offer native “coroutines”, but weaker programming models.

Reactive may be the best that people can do with the current JVM, but Loom’s goal is to do better, which we can do by making threads lighter and more scalable and letting people keep using the simple models and tooling they have been using for years but gaining a lot of performance. 

Virtual Threads
 

Threads without Baggage

  • Virtual threads are lightweight threads,
  • Not tied to the platform thread
  • They store virtual thread stacks as continuations in memory.

From a user perspective looms as there are almost no new APIs. The new runtime constrictor is the virtual thread that shares today’s thread’s APIs and semantics. We still implement them differently from the existing platform threads rather than tying to OS threads and carrying around a large contagious stack. 

Virtual threads store their stack in a heap of memory in defined limited configurations form. 

Wheelchair thread vs. Platform thread

The metadata in a wheelchair thread starts much faster than a platform thread and only uses as much stacked space as necessary for the active stack frames.

As a result, we can create many more virtual threads that we can plot from threads. When a virtual thread has work to do, the JVM assigns it an operating system carrier thread until it reaches a point where it would normally wait for some resource. JVM will then move the virtual thread out of the carrier thread and place instead some other virtual thread ready to get work done.

If the resource that fits was working on responses, we could allocate the thread to another carrier operating system thread. It could be the same carrier thread or a different one. It doesn’t matter. JVM handles the whole scheduling of virtual threads.

Using Loom
 

Small-to-none change for developers

  • Virtual Thread implements Java.lang.Thread and supports ThreadLocal
  • Clean stack traces and thread dumps
  • Sequential step-debugging
  • Profiling 

As a developer, we don’t need to worry about it. We can write code as we did before and let the JVM figure out when to have one of the virtual threads running, when to parse it, and when and how to bring it back to more work because the real java threads are not some other abstraction existing code tools that use the threads API just works when running on virtual threads. Working with local threads gives you the same stack of traces and thread dumps, and they work with the same debugger and profiling tools.

The JVM creates more platform threads, but if we continue adding platform threads that require so much memory even if they are not using it, we would run out of memory long before we could approach the point where the CPU is being properly utilized. 

Medium work (but easier than alternative) for Framework/tool providers 

The Helidon Team prototyped a replacement using a loom and called it Wrap. 

  • 1.5 people for 6 weeks(experimental)
  • With “dumb” synchronous code on the Loom and some simple tuning quickly got a similar or better performance to Netty.

We are a small group of people from the Helidon team, and by small, we mean one fulfilling person and one person part-time work to create a prototype replacement for Netty, which they called Wrap.

Netty is used to create high-performance protocols servers and clients to allow for optimal resource utilization. Nearly doesn’t use the simple paradigm of one java thread per request instead, it has its own set of APIs to allow multiple requests to time share the same operating system threads. 

Netty has been a mature product actively maintained since 2004, so it has about 18 years’ worth of hard work and optimizations using the Loom. 

Making a Loom
 

Massive Revamping of blocking code in JDK

  • All blocking points of JDK have to be upgraded to work with virtual threads;
  • Instead of blocking a thread that runs in a virtual thread, pause the virtual threads instead.
  • All new implementations to block IO (FileInputStream, FileOutputStream, Socket, DatagramSocket, SocketChannel, etc.)
  • Revised implementation of concurrency frameworks (Executors, Synchronous Queue, ThreadLocal), reflection, StackWalker, etc.

A considerable deal of the work on Loom had nothing to do with stack swapping magic, but with clearing roadblocks in the JDK to make it all work smoothly, this meant finding all the places in the JDK where blocking might happen and replacing it with logic that if running on virtual threads instead parse the virtual threads and free up the carrier threads.

They undertook many of these in separate features where individual JDK components were reimplemented and put into JDK 13, 15, and 18. This had the extra benefit of not only making this mechanism Loom ready but it also cleared up a bunch of technical debt on some complete code. 

In the end, the Loom made almost no significant changes to the JDK API except for the newly structured concurrency API. Instead, it provides a better implementation of existing and familiar abstractions.

Loom - Timeline

  • Late 2017 - Work on Loom begins
  • July 2019 - EA build of fiber prototype released for feedback.
  • Sept 2019 - JEP 353 (Reimplement Legacy Socket API) shipped to JDK 13
  • Sep 2020 - JEP 373 (Reimplement Legacy DatagramSocket API) shipped to JDK 15
  • Nov 2021 - EA build for structured concurrency support release or feedback
  • Nov 2021 - Draft JEPs for virtual threads and structured concurrency published for comment
  • March 2022 - JEP 418 (Internet Address Resolution SPI) shipped to JDK 18
  • Sep 2022 - Preview in JDK 19

Java has been working on the project loom since late 2017. A prototype of the core feature, then called fibers, is now called virtual treads in the first 18 months, but as usual, most of the work of getting to the production quality release was not in the core features itself but in the things adjacent to it. It took several literators to get the performance we wanted, and they rewrote much of the work in the implementation of Java’s old IO subsystems to illuminate blocking and get the debugger experience just tight. Some of these improvements have already shipped as part of separate features. 

Summary

Now we are coming to the end of the article, where we discuss what we learned. Here, this article is more functional than technical. It covers how this project looms into the picture: its advantages, scope from small to medium developers/framework/tool providers, and timeline from where it started till JDK 19. 

Stay tuned, we will soon be sharing another article on other new features of Java 19. Till then, enjoy reading the other articles.