StackTraces
Java已驗證

Java OOM unable to create new native thread

@debug_master32 天前更新閱讀 6 min00

一句話先說結論:這個報錯和 Java 堆沒關係。它說的是 JVM 找操作系統申請原生執行緒失敗,原因要麼是執行緒數頂到 OS 上限,要麼是執行緒棧把 native 記憶體吃空了。先查 ulimit -u 和執行緒數,再決定調大限制還是減執行緒。

背景

Java 的每個執行緒,背後都對應一個操作系統原生執行緒。原生執行緒的開銷不進 Java 堆,走的是 native 記憶體。

執行緒棧是 native 記憶體的大頭。64 位 JVM 預設一個執行緒棧約 1 MB。堆可能還有一半空著,執行緒數一多,native 記憶體先扛不住。

所以這個報錯經常出現在監控顯示「記憶體還很充足」的時候,容易讓人一頭霧水。

現象

執行緒池或壓測腳本不停開執行緒,跑到某一刻報錯:

java.lang.OutOfMemoryError: unable to create new native thread
    at java.base/java.lang.Thread.start0(Native Method)
    at java.base/java.lang.Thread.start(Thread.java:809)
    at java.base/java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:931)
    at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
    at com.example.task.AsyncTaskRunner.submit(AsyncTaskRunner.java:26)
    at com.example.service.OrderService.placeOrder(OrderService.java:44)
    at com.example.controller.OrderController.create(OrderController.java:31)

堆疊落在 Thread.start0,這是開原生執行緒的入口。注意它和 OutOfMemoryError: Java heap space 是兩碼事,後者才是堆滿。

根因分析

兩類原因,按頻率排。

1. 執行緒數撞上操作系統的上限

Linux 把進程和執行緒當同一種東西管。下面的限制直接影響能開多少執行緒:

  • ulimit -u:每個用戶能開的進程/執行緒數,常見預設是 1024。
  • kernel.threads-max:系統級執行緒總數上限。
  • kernel.pid_max:最大 PID,也間接卡住執行緒數。
  • 容器裡的 cgroup pids 限制。
  • systemd 管理的服務,另有一個 UserTasksMax。

2. native 記憶體被執行緒棧耗盡

每個執行緒棧預設約 1 MB。開 1 萬個執行緒,光棧就要約 10 GB native 記憶體。執行緒多,或者 -Xss 設太大,都會把 native 記憶體耗光。

排查步驟

先確認是哪種原因。

看用戶執行緒上限:

ulimit -u

看系統級參數:

cat /proc/sys/kernel/threads-max
cat /proc/sys/kernel/pid_max
cat /proc/sys/vm/max_map_count

數一下當前進程開了多少執行緒:

jstack <pid> | grep "java.lang.Thread.State" | wc -l

執行緒數貼近限制,基本就定位了。

解決方案

按根因對症下藥。

調高用戶執行緒上限

臨時放開,讓這個 shell 能開更多執行緒:

ulimit -u 8192

永久改,寫 /etc/security/limits.conf:

foo hard nproc 50000
foo soft nproc 50000
foo hard nofile 60000
foo soft nofile 60000

調內核參數

系統級上限不夠時:

sysctl -w kernel.threads-max=100000
sysctl -w kernel.pid_max=400000
sysctl -w vm.max_map_count=500000

寫進 /etc/sysctl.conf 才能重啟後生效。

systemd 服務改 UserTasksMax

用 systemd 拉起的服務,檢查 /etc/systemd/system.conf:

UserTasksMax=60000

改完執行 systemctl daemon-reload。

從根上減執行緒

調限制是治標,治本是少開執行緒。

  • 把無界的 newCachedThreadPool 換成有界執行緒池。
  • 控制壓測和併發度。
  • 真有大量併發,Java 21 的虛擬執行緒更合適,它不佔原生執行緒。

減小執行緒棧

確需大量原生執行緒時,可以壓縮單個棧:

java -Xss512k ...

預設 1 MB,降到 512 KB 能讓同等 native 記憶體容納更多執行緒。但棧太小有 StackOverflowError 風險,得實測。

思考過程

排查時最容易犯的錯,是看到 OutOfMemoryError 就調大 -Xmx。這個錯誤不歸 -Xmx 管,調了沒用。

先分清「堆滿」和「執行緒滿」是兩類問題。堆滿看 Java heap space,執行緒滿是 unable to create new native thread。前者加堆,後者查 OS 限制和執行緒數。

容器環境還多一層:宿主機 ulimit -u 放開了,但容器 cgroup 的 pids 限制沒調,照樣開不了執行緒。

小結

  • 這個報錯是 OS 拒絕創建新原生執行緒,和 Java 堆無關。
  • 根因:執行緒數頂到 ulimit/內核/容器限制,或執行緒棧耗光 native 記憶體。
  • 先 ulimit -u 加數執行緒定位,再決定調限制還是減執行緒。
  • 治本是少開執行緒,或上虛擬執行緒。
  • 別拿 -Xmx 去修,方向錯了。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法