Is Java Virtual Threads lightweight?

Quick answer is ‘yes’ :-). Before you try to understand how lightweight Java virtual threads are, you might want to understand how Java virtual thread works? Here is a post that gives a quick introduction to Java virtual threads. We highly recommend you read the quick introduction post, before reading further. 


Video: To see the visual walk-through of this post, click below:


Java virtual threads performance impact are studied from following perspectives:

1. Memory

2. Thread Creation time

3. CPU

4. Execution Time

Let’s review these performance impacts in detail in this post.

1. Memory

To study the memory impact of Java virtual threads we have put together a below two programs:

Program 1: Create 10,000 Platform Threads

public class PlatformThreadDemo {

   private static class Task implements Runnable {

      public void run() {

         try {

            Thread.sleep(600_000); // Sleep for 10 minutes

         } catch (Exception e) {

         }

      }

   }

   public static void main(String args[]) throws Exception {

      // Create 10, 000 platform threads

      for (int counter = 0; counter < 10_000; ++counter) {

         Thread myThread = new Thread(new Task());

         myThread.start();

      }

      Thread.sleep(600_000);

   }

}

In this program, we are creating 10,000 platform threads and putting them to sleep for 10 minutes. Note: Most threads in the application spend the vast majority of its lifetime waiting, rather than doing actual real work. A thread typically 

  1. Waits for a new request to come in
  2. Waits for the backend response
  3. Waits for the disk I/O
  4. Waits for the network I/O
  5. Waits when another thread has acquired a lock

Thus, to simulate the wait, we are putting the threads to sleep.

Program 2: Create 10,000 Virtual Threads

public class VirtualThreadDemo {

   private static class Task implements Runnable {

      public void run() {

         try {

            Thread.sleep(600_000); // Sleep for 10 minutes

         } catch (Exception e) {

         }

      }

   }

   public static void main(String args[]) throws Exception {

      // Create 10, 000 platform threads

      for (int counter = 0; counter < 10_000; ++counter) {

         Thread.ofVirtual().start(new Task());

      }

      Thread.sleep(600_000);

   }

}

This program is very similar to the previous program. Only difference is we are creating 10, 000 virtual threads instead of platform threads and putting them to sleep for 10 minutes

Now, we executed both programs simultaneously in an AWS t3a.xlarge EC2 instance. We configured the thread stack size to be 1mb (by passing JVM argument -Xss1m). This argument indicates that every thread in this application should be allocated 1mb of stack size(i.e. memory). For a much more detailed insight about the thread stack size, you may refer to this blog post. 

Below is the ‘top’ command output of the platform threads program. It shows the memory consumption of the platform threads program:

memory consumption of Virtual Threads and Platform Threads

Fig: top command showing memory consumption of Virtual Threads and Platform Threads

You can notice that the virtual threads program only occupies 4.4mb (i.e., 4464796 bytes), whereas the platform threads program occupies 14.3gb. This clearly indicates that virtual threads consume comparatively much lesser memory. 

To understand why it consumes such less memory, you might need to understand the virtual threads architecture. But in nutshell, whenever a platform thread is created, an underlying operating system thread is allocated to it. This operating system thread is locked to the same platform thread until platform thread exits the JVM. Even if the platform thread sleeps and doesn’t do any real work, the operating system thread will not be allowed to do any other work. Since we are launching the program with thread’s stack size as 1mb, threads alone in this program will consume 10gb (10,000 threads x 1mb stack size = 10,000mb, which is 10gb) of memory.

On the other hand, virtual threads are stored in the Java heap memory region just like any other object (aka stack chunk object). This stack chunk object doesn’t occupy much memory. Only when a virtual thread needs to do real work, it’s tied up to the underlying operating system. Since in the above example, all the threads are sleeping and not doing any real work it will not lock up any operating system thread. Thus, virtual threads comparatively occupy much less memory than platform threads.

2. Thread Creation Time

To study the thread creation time impact, we put together below two sample programs.

public class PlatformThreadFactory {

   private static class Task implements Runnable {

      @Override

      public void run() {

         System.out.println("Hello! I am a Platform Thread");

      }

   }

   public static void main(String[] args) throws Exception {

      long startTime = System.currentTimeMillis();

      for (int counter = 0; counter < 10_000; ++counter) {

         new Thread(new Task()).start();

      }

      System.out.print("Platform Thread Creation Time: " + (System.currentTimeMillis() - startTime));

   }

}

 The above ‘PlatformThreadFactory’ program launches 10,000 platform threads. Each thread prints ‘Hello! I am a Platform Thread’ and then the program exits. 

public class VirtualThreadFactory {

   private static class Task implements Runnable {

      @Override

      public void run() {

         System.out.println("Hello! I am a Virtual Thread");

      }

   }

   public static void main(String[] args) throws Exception {

      long startTime = System.currentTimeMillis();

      for (int counter = 0; counter < 10_000; ++counter) {

         Thread.startVirtualThread(new Task());

      }

      System.out.println("Virtual Thread Creation Time (ms): " + (System.currentTimeMillis() - startTime));

   }

}

The above ‘VirtualThreadFactory’ program launches 10,000 platform threads. Each thread prints ‘Hello! I am a Virtual Thread’ and then the program exits. 

Below is the table that summarizes execution time of these two programs:

Virtual ThreadsPlatform Threads
Execution Time91 ms998 ms

You can see that virtual Thread took only 91ms to be complete, whereas Platform Thread took almost 10x more i.e., 998ms. It’s because platform threads are more expensive to create. Because whenever a platform needs to be created an operating system thread needs to be allocated to it. Creating and allocating an operating system thread is not a cheap operation. 

Based on this discussion, don’t jump to the conclusion that virtual threads are much faster than platform threads. No, they are not. It’s just that virtual threads creation time is much faster (see the ‘Execution Time’ section below). 

3. CPU

Virtual thread is not faster than platform thread. Whenever a virtual thread needs to execute a real work, it needs to be tied up to a platform thread. Thus, a virtual thread tends to consume the same amount of CPU as the platform thread.

4. Execution Time

As mentioned earlier, whenever a virtual thread needs to execute a real work, it needs to be tied up to a platform thread. Thus, at the end of the day – a real execution of the job is done by the platform thread. Due to that, there wouldn’t be any difference in the execution time between virtual and platform threads.

Conclusion

Based on this study we can conclude that:

  1. If your application either has lot of threads or a large stack size (i.e. -Xss), then switching to virtual threads would reduce your application’s memory consumption. 
  2. If your application is creating new threads frequently (instead of leveraging thread pool), switching to virtual threads can improve your response time.

Besides these two optimizations, switching to virtual threads has a potential to improve your application availability, throughput and code quality. To learn more about the benefits of virtual threads you may refer to this post.

Share your Thoughts!

Up ↑

Discover more from yCrash

Subscribe now to keep reading and get access to the full archive.

Continue reading