Understanding Java Virtual Threads: Construction, Performance, and Best Practices
This article explains Java virtual threads introduced by Project Loom, compares them with platform threads in terms of memory usage and creation time, provides code examples for creating and managing virtual threads, and outlines practical guidelines and use‑cases for high‑concurrency backend applications.
Although the Java platform is approaching its 30th anniversary, it remains one of the three most popular programming languages, largely thanks to the Java Virtual Machine (JVM) which abstracts complexities such as memory management and compiles code at runtime to achieve unparalleled internet‑scale scalability.
Java’s continued relevance is also driven by rapid evolution of the language, its libraries, and the JVM; Project Loom’s introduction of virtual threads represents a breakthrough in how Java handles concurrency.
Exploring Thread Construction: Revealing the Nature of Threads
A thread is the smallest schedulable processing unit that runs in parallel with other units; it is an instance of java.lang.Thread . Java defines two thread types: platform threads and virtual threads.
Platform Threads
Platform threads are lightweight wrappers around operating‑system (OS) threads. They run Java code on an underlying OS thread until the thread’s lifecycle ends, so the number of platform threads is limited by the number of OS threads. They have larger stacks and other OS‑managed resources, making them suitable for all task types but potentially limited in quantity.
Virtual Threads
Virtual threads are not bound to a specific OS thread, although they still execute on OS threads. When a virtual thread encounters a blocking I/O operation, it pauses, allowing the OS thread to handle other work. Similar to virtual memory, many virtual threads are multiplexed onto a smaller set of OS threads. They excel at I/O‑heavy workloads but are not ideal for sustained CPU‑bound tasks, offering lightweight concurrency that simplifies development, maintenance, and debugging of high‑throughput applications.
Comparing Thread Construction: Virtual Threads vs Platform Threads
Below is a visual comparison of platform and virtual threads.
Creating Virtual Threads
Using the Thread class and Thread.Builder interface
The following example creates and starts a virtual thread that prints a message, using join to wait for completion before the main thread exits:
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello World!! I am Virtual Thread")); thread.join();The Thread.Builder interface lets you set common thread properties such as a name.
Example of creating a named virtual thread "MyVirtualThread":
Thread.Builder builder = Thread.ofVirtual().name("MyVirtualThread"); Runnable task = () -> { System.out.println("Thread running"); }; Thread t = builder.start(task); System.out.println("Thread name is: " + t.getName()); t.join();Using Executors.newVirtualThreadPerTaskExecutor()
An executor decouples thread creation and management from other application parts. The following code creates an ExecutorService that spawns a new virtual thread for each submitted task and returns a Future that can be awaited:
try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) { Future
future = myExecutor.submit(() -> System.out.println("Running thread")); future.get(); System.out.println("Task completed"); }Is Your Thread Construction Lightweight? Advantages of Virtual Threads
Memory
Program 1 – creating 10,000 platform threads:
public class PlatformThreadMemoryAnalyzer { private static class MyTask implements Runnable { @Override public void run() { try { Thread.sleep(600000); } catch (InterruptedException e) { System.err.println("Interrupted Exception!!"); } } } public static void main(String[] args) throws Exception { int i = 0; while (i < 10000) { Thread myThread = new Thread(new MyTask()); myThread.start(); i++; } Thread.sleep(600000); } }Program 2 – creating 10,000 virtual threads:
public class VirtualThreadMemoryAnalyzer { private static class MyTask implements Runnable { @Override public void run() { try { Thread.sleep(600000); } catch (InterruptedException e) { System.err.println("Interrupted Exception!!"); } } } public static void main(String[] args) throws Exception { int i = 0; while (i < 10000) { Thread.ofVirtual().start(new MyTask()); i++; } Thread.sleep(600000); } }Running both programs on a RedHat VM with -Xss1m (1 MB stack per thread) yields the memory usage shown below:
The virtual‑thread program consumes only about 7.8 MB, whereas the platform‑thread program uses roughly 19.2 GB, clearly demonstrating the memory efficiency of virtual threads.
Thread Creation Time
Program 1 – launching 10,000 platform threads:
public class PlatformThreadCreationTimeAnalyzer { 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 i = 0; i < 10_000; i++) { new Thread(new Task()).start(); } System.out.print("Platform Thread Creation Time: " + (System.currentTimeMillis() - startTime)); } }Program 2 – launching 10,000 virtual threads:
public class VirtualThreadCreationTimeAnalyzer { 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 i = 0; i < 10_000; i++) { Thread.startVirtualThread(new Task()); } System.out.print("Virtual Thread Creation Time: " + (System.currentTimeMillis() - startTime)); } }The timing summary (image) shows virtual threads completing in about 84 ms, while platform threads take roughly 346 ms, highlighting the higher creation cost of platform threads.
Refactoring Thread Construction: Applications of Virtual Threads
Virtual threads can greatly benefit many application types, especially those requiring high concurrency and efficient resource management:
Web servers : handle massive simultaneous HTTP requests with less overhead than traditional thread pools.
Microservices : many I/O‑bound operations such as database queries and network calls become more efficient.
Data processing : concurrent processing of large data sets gains throughput and performance.
Avoiding Pitfalls
To get the most out of virtual threads, consider the following best practices:
Avoid synchronized blocks/methods; blocking on a virtual thread may not release the underlying OS thread.
Do not place virtual threads in traditional thread pools; the JVM already manages them efficiently.
Minimize use of ThreadLocal ; millions of virtual threads each holding a ThreadLocal can quickly exhaust heap memory.
Conclusion
Java virtual threads are lightweight threads implemented by the Java runtime rather than the operating system. Unlike platform threads, they can scale to millions within a single JVM, enabling server applications that allocate one thread per request to achieve higher concurrency, throughput, and hardware utilization.
Developers familiar with java.lang.Thread since Java SE 1.0 can adopt virtual threads with the same programming model, but must adjust practices that were optimal for costly platform threads.
Cognitive Technology Team
Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.