Wednesday, July 23, 2025

Java Platform Threads Explained: Are They Too Expensive to Scale? (Hands-On Guide)

Wondering why Java applications struggle to scale with thousands of concurrent tasks?

The answer lies in the cost of using traditional Java threads, known as platform threads. In this blog post, we’ll explore how platform threads behave under load, the limitations they pose, and why developers are shifting to Java’s new virtual threads for scalability.


Platform threads in Java are heavyweight OS threads that consume a significant amount of memory (typically 1MB stack each), making them expensive to create and manage at scale. Beyond a certain threshold, the JVM throws errors like OutOfMemoryError: unable to create new native thread.

 

๐Ÿงต What Are Platform Threads in Java?

A platform thread is Java’s traditional thread backed by a native operating system thread. Each thread:

  • Is created using the new Thread() API
  • Requires dedicated stack memory (~1MB by default)
  • Is limited by OS-level thread creation limits

๐Ÿ’ก Key Characteristics of Platform Threads:

 | Feature             | Platform Thread                       |
 | ------------------- | ------------------------------------- |
 | Backed By           | OS native thread (e.g., POSIX thread) |
 | Stack Memory        | \~1MB (adjustable with `-Xss`)        |
 | Creation Cost       | High                                  |
 | Scalability         | Limited                               |
 | Blocking Operations | Blocks the OS thread                  |
 

๐ŸŽฏ Why Understanding Thread Creation Cost Matters

In microservice architectures, Java apps make frequent network calls and I/O-bound operations. These operations often block threads, making them idle while waiting for responses.

To handle high concurrency, developers often try to create more threads — but platform threads come at a cost:

  • High memory usage per thread
  • OS-imposed limits on number of threads
  • Performance bottlenecks under load

Let’s explore this with a practical example.


๐Ÿ› ️ Java Thread Creation Demo – Hands-On


We'll simulate a typical I/O-heavy workload using Thread.sleep() to mimic latency, and then create thousands of threads to see what happens.

✅ Project Setup

  • Java Version: 21 (for Duration.ofSeconds)
  • Tools: IntelliJ IDEA or any Java IDE


๐Ÿงฑ Simulate I/O With Thread.sleep()


 public class Task {
     private static final Logger logger = LogManager.getLogger(Task.class);

     public static void ioIntensive(int i) {
         logger.info("Starting IO Task: " + i + " - " + Thread.currentThread().getName());
         try {
             Thread.sleep(Duration.ofSeconds(10));
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
         logger.info("Ending IO Task: " + i + " - " + Thread.currentThread().getName());
     }
 }
 

๐Ÿ” Why Thread.sleep()?

Though it’s not a real network call, it effectively simulates thread blocking, allowing us to observe memory and OS behavior without external dependencies.


๐Ÿ” Creating Threads in a Loop


 public class InboundOutboundTaskDemo {
     private static final int MAX_PLATFORM_THREADS = 50_000;

     public static void main(String[] args) {
         for (int i = 0; i < MAX_PLATFORM_THREADS; i++) {
             final int taskNumber = i;
             Thread thread = new Thread(() -> Task.ioIntensive(taskNumber));
             thread.start();
         }
     }
 }
 

Run the code with MAX_PLATFORM_THREADS = 10 — it works fine.

Increase it to 50,000, and you’ll likely see:


Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread


๐Ÿ“‰ Why Does Thread Creation Fail?

Let’s break it down.

๐Ÿ” Behind the Scenes

  • When you create a thread:
  • JVM uses native OS call pthread_create() (on Linux/macOS)
  • Each thread gets a default stack of ~1MB
  • OS imposes a per-process limit on thread count

Even if you have available heap memory, thread creation can still fail due to:

  • Native memory exhaustion
  • Reaching user-level thread limits

๐Ÿ“Š Java Platform Thread Limits: A Quick Comparison


| Resource                | Platform Thread | Virtual Thread (Preview) |
| ----------------------- | --------------- | ------------------------ |
| Memory per Thread       | \~1MB           | Few KB                   |
| Max Threads in Practice | \~8,000–15,000  | 1,000,000+               |
| Blocking Cost           | High            | Minimal (non-blocking)   |
| Backed by OS Thread?    | Yes             | No (uses carrier thread) |
| Thread Creation Speed   | Slow            | Fast                     |


๐Ÿ“Œ Tuning Tips: Can We Create More Platform Threads?


Yes, but with caveats.

✅ Use JVM Flags


 java -Xss256k InboundOutboundTaskDemo
 

This reduces thread stack size to 256KB (vs 1MB default), allowing more threads.


✅ Use Thread Pools

Instead of raw new Thread(), prefer:


 ExecutorService executor = Executors.newFixedThreadPool(200);
 

But even with thread pools, you’re limited by blocked threads during I/O calls.


๐Ÿง  Alternative: Enter Virtual Threads (Project Loom)

Java 21 introduced virtual threads as a preview feature. These threads are:

  • Lightweight
  • Not backed by OS threads
  • Scalable to millions of concurrent tasks

Stay tuned — in the next post, we’ll rewrite this same example using virtual threads and compare memory usage, performance, and thread behavior.


✅ Pros and Cons of Java Platform Threads

๐Ÿ‘ Pros:

Mature and battle-tested

Great for CPU-bound tasks

Full integration with debuggers, profilers, and logging


๐Ÿ‘Ž Cons:

High memory usage per thread

Not suitable for high-concurrency I/O apps

Blocking operations are expensive


๐Ÿ”„ Summary: Should You Still Use Platform Threads?

Use platform threads when:

  • You’re doing CPU-bound tasks
  • The number of concurrent threads is low to moderate
  • You need deep integration with existing thread tools


Avoid platform threads when:

  • You're building a high-throughput microservice
  • The app makes lots of blocking I/O calls
  • You need to handle hundreds of thousands of concurrent users


Helpful Links


Java Virtual Threads Tutorial (coming soon)


๐Ÿ“Œ Final Thoughts


Platform threads are reliable — but expensive. They don't scale well in modern, I/O-heavy applications.

Understanding their limitations is the first step toward embracing virtual threads, the future of Java concurrency.

➡️ Ready to explore virtual threads? Read Part 2: Java Virtual Threads vs Platform Threads – Full Benchmark and Code

No comments:

Post a Comment

Please do not add any spam links in the comments section.