Backend Development 12 min read

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.

Cognitive Technology Team
Cognitive Technology Team
Cognitive Technology Team
Understanding Java Virtual Threads: Construction, Performance, and Best Practices

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.

JavaJVMperformanceconcurrencyVirtual Threadsthreading
Cognitive Technology Team
Written by

Cognitive Technology Team

Cognitive Technology Team regularly delivers the latest IT news, original content, programming tutorials and experience sharing, with daily perks awaiting you.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.