We use essential cookies for login and local storage for your preferences. For usage metrics, we use Google Analytics 4 — you can decline without losing anything in the product. Learn more

ThreadMineThreadMine
Back

Error message

java.lang.OutOfMemoryError: unable to create native thread

Quick answer

Probable causeThe OS refused to create another thread: a leak, a process or container limit, or native memory. Not the heap: a bigger -Xmx makes it worse.

Next stepCount the threads. kill -3 prints the thread dump to the JVM's stdout.

Thread dump

kill -3 <pid>
Analyze my dump now

The log line (JDK 11 and later)

Exception in thread "http-nio-8080-exec-7" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached
	at java.base/java.lang.Thread.start0(Native Method)
	at java.base/java.lang.Thread.start(Thread.java:1417)
	...

What the message means

The JVM asked the operating system for a new thread and the request failed. On Linux that call is pthread_create. The JVM reports the failure as an OutOfMemoryError, but it is about the process, not about the heap.

The wording depends on the JDK. JDK 8 prints unable to create new native thread. JDK 11, 17 and 25 print unable to create native thread: possibly out of memory or process/resource limits reached. Both mean the same.

Two consequences: a heap dump will not show the cause, and a larger -Xmx can make the error more frequent, because the stacks of the threads live outside the heap and the heap takes memory from them.

Common causes

  1. A thread leak. Code that creates an executor, or a thread, on every call and never shuts it down. The thread count grows until the limit. This is the most common cause in applications that ran well for days.
  2. The user process limit. ulimit -u caps the threads of a user, and every thread counts as a process for this limit.
  3. The container limit. Docker --pids-limit, the Kubernetes pod PID limit or pids.max in the cgroup. The limit is on the container, so other processes in it count too.
  4. System limits. kernel.threads-max, kernel.pid_max and vm.max_map_count.
  5. No native memory left. Each platform thread reserves a stack (-Xss, 1 MB by default on Linux x64). With enough threads the stacks alone use the memory the container has.

Capture the evidence

First count the threads and compare with the limits. Run this on the same host or container as the JVM:

Thread count and limits

grep Threads /proc/<pid>/status
ulimit -u
cat /sys/fs/cgroup/pids.current /sys/fs/cgroup/pids.max

Threads: in /proc/<pid>/status is the number of threads of the process. Then take the dump. At the limit, jstack and jcmd can fail because they need the JVM to create a thread to answer. kill -3 <pid> does not: the JVM writes the thread dump to its own stdout, so read it from the console or the container log. More ways to capture one: how to take a thread dump.

Got the dump? Paste it and see which thread names pile up.

Analyze my dump now

Dump with sensitive data? Anonymize it first with tm-anon.

What it looks like in a dump

A thread leak shows as thousands of threads with the same stack and a name that counts up. This is a real dump from our reproduction (JDK 25), where the code created a new executor per call. Each thread is idle, parked in the queue of its own executor:

One of the idle pool threads

"pool-1-thread-1" #26 [54084] prio=5 os_prio=0 cpu=0.00ms elapsed=4.99s tid=0x000000b3ef02c230 nid=54084 waiting on condition  [0x000000b3eafee000]
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(java.base@25.0.2/Native Method)
	- parking to wait for  <0x000000060ec0b3a0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(java.base@25.0.2/LockSupport.java:369)
	...
	at java.util.concurrent.LinkedBlockingQueue.take(java.base@25.0.2/LinkedBlockingQueue.java:435)
	at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@25.0.2/ThreadPoolExecutor.java:1016)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@25.0.2/ThreadPoolExecutor.java:1076)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@25.0.2/ThreadPoolExecutor.java:614)
	at java.lang.Thread.runWith(java.base@25.0.2/Thread.java:1487)
	at java.lang.Thread.run(java.base@25.0.2/Thread.java:1474)

The sign is in the names. Each one is thread-1 of a different pool, 150 pools in this dump:

Names in the dump

"pool-1-thread-1"
"pool-2-thread-1"
"pool-3-thread-1"
...
"pool-150-thread-1"

ThreadMine counts the threads by the name prefix and reports the pattern:

Critical

Executor leak: 150 separate thread pools with default names

150 threads

150 threads named pool-N-thread-M across 150 pools. A new executor is probably created per call and never shut down.

By hand, count the threads by the name without the number and look for one that dominates. A name that repeats thousands of times is the leak.

How to fix it

Find the cause with the count and the dump, then fix that. Changing limits comes last.

Shut down what you create

Every executor you create needs shutdown(). From Java 19 ExecutorService is AutoCloseable, so try-with-resources does it:

try-with-resources (Java 19+)

try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
    executor.submit(task);
}   // close() waits for the tasks and stops the threads (Java 19+)

Share a bounded pool

Create the executor once, as a bean or a field, with a fixed size. Do not create one per request.

Name the threads

Pass a ThreadFactory that names the threads after what they do. A leak with a real name is found in one look at the dump, and pool-N-thread-M hides who created it.

Raise the limit, last

Raise ulimit -u, the container PID limit or the system limits only after you ruled out a leak. With a leak, a higher limit only delays the same error. If the count is high and legitimate, lower -Xss or reduce the pool sizes before raising anything.

More about reading a dump of a stuck application: Spring Boot stuck threads.

Frequently asked questions

Is "unable to create native thread" a heap problem?

No. The heap still has room. The JVM asked the operating system for a new thread (pthread_create on Linux) and the call failed. A heap dump will not help, and a larger -Xmx leaves less memory for thread stacks, so it can make the error more frequent.

What is the difference between "unable to create new native thread" and "unable to create native thread"?

Only the wording. JDK 8 prints "unable to create new native thread". JDK 11, 17 and 25 print "unable to create native thread: possibly out of memory or process/resource limits reached". The cause is the same, so the same fixes apply.

Why does jstack fail when this happens?

jstack and jcmd attach to the JVM through a helper thread, and at the limit the JVM may not be able to create it. kill -3 <pid> does not need to attach: the JVM prints the thread dump to its own stdout, so look in the console or the container log.

How many threads can a JVM handle?

There is no fixed number. The limit is whichever of these is reached first: the user process limit (ulimit -u), the container PID limit, the system limits (threads-max, pid_max, vm.max_map_count) and the native memory left for stacks, roughly the stack size (-Xss) times the number of threads. A healthy service rarely needs thousands of platform threads.

How do I see the thread limit of my container?

Inside the container, cat /sys/fs/cgroup/pids.max shows the limit and pids.current the current count (cgroup v2). Docker sets it with --pids-limit, and Kubernetes with the pod PID limit. If pids.current is close to pids.max, the container limit is what you hit.

Got the dump of the JVM at the limit?

Paste it and see which thread names pile up. No signup for the first dump.