Platform threads typically have a large thread stack and other resources that are maintained by the operating system. Virtual threads are lightweight threads that reduce the effort of writing, maintaining, and debugging high-throughput concurrent applications. Virtual threads allow IO-bound thread-per-task applications to scale better by removing the most common scaling bottleneck — the maximum number of threads — which in turn enables better hardware utilization. Java 19 brings the first preview of virtual threads to the Java platform; this is the main deliverable of OpenJDKs Project Loom.
The new diagnostics help to migrate code to virtual threads and to assess whether a specific use of synchronization should be replaced with java.util.concurrentLock. A virtual thread is an instance of java.lang.Thread that is not bound to a specific operating system thread. In contrast, a platform thread is an instance of java.lang.Thread, implemented in the traditional way as a streamlined wrapper around the operating system thread. The goal is not to remove legacy implementations of threads or to silently migrate existing applications to use virtual threads.
We are creating this task to keep the example simple so we can focus on the concept. Spring Runtime offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription. These results are unscientific, but the difference in runtimes is substantial. Listing 1 shows the changes I made to the Maven archetype’s POM file. Note that I also set the compiler to use Java 19 and added a line to the .mvn/jvm.config.
Java Virtual Threads Explained
Another big issue is that such async programs are executed in different threads so it is very hard to debug or profile them. Traditionally, Java has treated the platform threads as thin wrappers around operating system threads. Creating such platform threads has always been costly , so Java has been using the thread pools to avoid the overhead in thread creation. We also believe that ReactiveX-style APIs remain a powerful way to compose concurrent logic and a natural way for dealing with streams. We see Virtual Threads complementing reactive programming models in removing barriers of blocking I/O while processing infinite streams using Virtual Threads purely remains a challenge.
The application code in turn creates two new virtual threads that obtain resources concurrently through the same ExecutorService as in the first example. Developers can choose whether to use virtual threads or platform threads. Here is an example program that creates a large number of virtual threads. The program first gets an ExecutorService which will create a new virtual thread for each task submitted.
Introducing virtual threads
This implicitly places a practical limit on how many threads we can create, which in turn has consequences for how we use threads in our programs. A new command that allows the debugger to test if a thread is a virtual thread. JNI defines a new function, IsVirtualThread, to test whether an object is a virtual thread.
The poller’s event loop, when woken up with an event, uses the event’s file descriptor to lookup the corresponding virtual thread and unparks it. A thread in Java is just a small wrapper around a thread that is managed and scheduled by the OS. Project Loom adds a new type of thread to Java called a virtual thread, and these are managed and scheduled by the JVM. When invoked on a virtual thread, Thread.getThreadGroup() returns a placeholder thread group with the name “VirtualThreads“. Use of Virtual Threads clearly is not limited to the direct reduction of memory footprints or an increase in concurrency. The introduction of Virtual Threads also prompts a broader revisit of decisions made for a runtime when only Platform Threads were available.
OS-level monitoring will observe that JDK processes use fewer OS threads than virtual threads. Other than code simplicity, what’s really powerful here is a unified way of handing error scenarios of different executions running in the completely different threads. While this topic, like all else in the multithreaded realm is complex and requires quite some time to master, the code snippet down below should be a good example of the structured concurrency in action. Summing things up, virtual threads are very exciting addition to the Java Platform and it seems that Oracle’s focus on simplicity, ease of use and interoperability will yield dividends. Virtual threads always have the normal priority and the priority cannot be changed, even with setPriority method.
- The program spawns 50 thousand iterations of whichever thread type you choose.
- This is a waste of computing resources and a major hurdle in achieving a high throughput application.
- But it’s nice to explore the new APIs and see what performance improvements it already gives us.
- The following is an example of such a thread dump, taken from an application similar to the second example above, presented in the JSON viewer .
Hence implementing https://globalcloudteam.com/ that, per Oracle, align perfectly with everything that currently exist in Java and in the future it should be the number one approach when building high scale thread-per-request style programs in Java. The synchronous Java networking APIs have been re-implemented by JEP 353 and JEP 373 in preparation for Project Loom. When run in a virtual thread, I/O operations that do not complete immediately will result in the virtual thread being parked. The implementation is using several features from the Java VM and the Core libraries to offer a scalable and efficient alternative that compares favorably with current asynchronous and non-blocking code constructs. Virtual threads typically employ a small set of platform threads that are used as carrier threads. Code executing in a virtual thread will usually not be aware of the underlying carrier thread.
1. DO NOT Pool the Virtual Threads
Frequent fixes over long periods of time may harm the scalability of the application by capturing operators. Something that other languages like Go or Erlang had for years or decades. The retrieveURLs method creates a list of tasks and submits them to the executor, then waits for the results.
The hang/resume implementation allows the debugger to hang and resume virtual threads, and allows carrier threads to be hung when a virtual thread is hung. The stop(), suspend(), and resume() methods are not supported for virtual threads. The constructor for the thread class definition creates platform threads, as before. The current limitation of virtual threads is that G1 GC does not support huge stack block objects. A StackOverflowError may be raised if the virtual thread’s stack reaches half the size of the region .
The method can also be called indirectly via the platform MBeanServer from a local or remote JMX tool. Jdk.VirtualThreadStart and jdk.VirtualThreadEnd indicate the start and end of a virtual thread. Future now defines methods to get the result or exception of a completed task and to get the status of the task. Combined, these additions make it easy to use the Future object as an element of a stream, filter the Future stream to find completed tasks, and then map to get the result stream.
Consequently, the number of available platform threads is limited to the number of OS threads. Mounting a virtual thread means temporarily copying the needed stack frames from the heap to the stack of the carrier thread, and borrowing the carriers stack while it is mounted. Virtual threads are an alternative implementation of java.lang.Thread which store their stack frames in Javas garbage-collected heap rather than in monolithic blocks of memory allocated by the operating system. Virtual threads breathe new life into the familiar thread-per-request style of programming, allowing it to scale with near-optimal hardware utilization. Virtual threads reduce the effort of writing, maintaining, and observing high-throughput concurrent applications.
Where Virtual Threads make sense
Subgroups offer specialized focus for things like classes, workshops, special interest groups, activities, and administrative needs such as meetings & committees. As a best practice, if a method is used very frequently and it uses a synchronized block then consider replacing it with the ReentrantLock mechanism. We very much look forward to our collective experience and feedback from applications.
It does not include object addresses, locks, JNI statistics, heap statistics, and other information that appears in traditional thread dumps. In addition, because it may need to list a large number of threads, generating a new thread dump does not suspend the application. The following is an example of such a thread dump, taken from an application similar to the second example above, presented in the JSON viewer . A virtual thread is an instance of java.lang.Thread that runs Java code on the underlying operating system thread, but does not capture operating system threads for the entire life cycle of the code.
Later, when the response arrives, the JVM will allocate another thread from the pool that will handle the response and so on. This way, multiple threads are involved in handling a single async request. The vast majority of blocking operations in the JDK will unload the virtual thread, freeing its carrier and the underlying OS thread to take on new work. However, some blocking operations in the JDK do not offload virtual threads, and therefore block their carriers and the underlying OS threads. This is due to the operating system level (e.g., many file system operations) or the JDK level (e.g., Object.wait()).
2. Avoid using Thread-local Variables
Existing JVM TI agents will work as before, but they may encounter errors if they call functions that are not supported by virtual threads. These occur when agents that are not aware of virtual threads are used with applications that use virtual threads. For some agents, changing GetAllThreads to return an array containing only platform threads may be a problem. Existing agents with ThreadStart and ThreadEnd events enabled may encounter performance issues because they are unable to restrict these events to platform threads. The new thread dump format lists virtual threads that are blocked in network I/O operations, as well as virtual threads created by the new-thread-per-task (one-task-one-thread) ExecutorService shown above.
Memory usage and interaction with garbage collection
Servers today can handle far larger numbers of open socket connections than the number of threads they can support, which creates both opportunities and challenges. To create a platform thread , you need to make a system call, and these are expensive. To create a virtual thread, you don’t have to make any system call, making these threads cheap to make when you need them. Behind the scenes, the JVM created a few platform threads for the virtual threads to run on. Since we are free of system calls and context switches, we can run thousands of virtual threads on just a few platform threads. For application programmers, they represent an alternative to asynchronous-style coding such as using callbacks or futures.
When used by programs, a richer relational structured concurrency between threads can be displayed. The virtual thread’s stack is stored in Java’s garbage collection heap as a stack block object. The stack grows and shrinks as the application runs, both for memory efficiency and to accommodate stacks of arbitrary depth (up to the JVM-configured platform thread stack size).
The executor starts a new virtual thread for each task, which calls getURL. Loom also added a new executor to the Concurrency API to create new virtual threads. The new VirtualThreadPerTaskExecutorreturns an executor that implements the ExecutorService interface just as the other executors do. Let’s start with an example of using theExecutors.newVirtualThreadPerTaskExecutor() method to obtain an ExecutorService that uses virtual threads. The easiest way to create a virtual thread is by using the Thread class.