The solution is the cost of platform threads, which are the traditional Java threads. In this blog, we will discuss the behavior of platform threads when loaded, limitations, and why developers are moving to the new virtual threads of Java in order to scale.
Java Platform Threads are Costly
Java platform threads are costly because of:
- OS-level memory
- Context switching
- Lifecycle costs
Java platform threads are heavyweight operating system threads, which:
- Use a lot of memory (usually 1MB stack memory)
- Are costly to create and operate at large scale
The JVM throws errors like:
OutOfMemoryError: unable to create new native thread
What are Platform Threads in Java?
Java has a traditional thread supported by a native operating system thread called a platform thread.
Each thread:
- Is created with new Thread API
- Takes dedicated stack memory (1MB by default)
- Is restricted by OS thread limits
Important Features of Platform Threads
Feature |
Platform Thread |
|---|---|
|
Backed By |
OS native thread (e.g., POSIX thread) |
|
Stack Memory |
Between 1MB and 10MB (-Xss) |
|
Creation Cost |
High |
|
Scalability |
Limited |
|
Blocking Operations |
Blocks the OS thread |
Why Thread Creation Cost Matters
Java apps in microservice architecture frequently perform I/O-bound operations.
These operations:
- Block threads
- Keep them idle until response
To handle high concurrency, developers try to:
Increase number of threads
But platform threads are expensive because:
- Large memory consumption per thread
- OS-imposed limits
- 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()?
It simulates thread blocking behavior, similar to network calls, 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();
}
}
}
Execution Result
- With 10 threads → Works fine
- With 50,000 threads → Likely error:
java.lang.OutOfMemoryError: unable to create new native thread
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
Why Does Thread Creation Fail?
When creating a thread:
- JVM calls native OS API (
pthread_create) - Each thread consumes ~1MB stack memory
- OS enforces thread limits per process
Even if heap memory is available, failure occurs due to:
- Native memory exhaustion
- OS thread limits
Java Platform Thread Limits: A Quick Comparison
|
Resource |
Platform Thread |
Virtual Thread |
|---|---|---|
|
Memory per Thread |
~1MB |
Few KB |
|
Max Threads |
~8,000–15,000 |
1,000,000+ |
|
Blocking Cost |
High |
Minimal |
|
OS Thread Dependency |
Yes |
No (carrier 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.
- Reuses threads
- But blocking still exists
Related: Thread Pool in Java
Alternative: 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.
Advantages and Disadvantages of Platform Threads
Pros
- Mature and stable
- Good for CPU-bound tasks
- Full support with debugging tools
Cons
- High memory usage
- Poor fit for high-concurrency I/O
- Blocking is expensive
When Should You Use Platform Threads?
Use When:
- CPU-bound workloads
- Limited concurrency
- Need deep tooling support
Avoid When:
- Building high-throughput microservices
- Heavy blocking I/O operations
- Handling hundreds of thousands of users
Helpful Links
Final Thoughts
Platform threads are powerful — but expensive.
They struggle with modern I/O-heavy workloads, making them less suitable for high-scale systems.
Their limitations led to the evolution of:
Virtual Threads — the future of Java concurrency
No comments:
Post a Comment
Please do not add any spam links in the comments section.