java.lang.OutOfMemoryError: unable to create new native thread問題排查以及當前系統最大程式數量

昀溪發表於2018-08-19

1. 問題描述

線上某應用出問題,檢視日誌

這一組伺服器是2臺,每臺都有。配置為64G,使用7G,空餘記憶體非常多

2. 問題排查

環境變化:程式遷移到新機器,新機器是CentOS 7,程式執行賬號由原來的root改為work。硬體配置由32G升級為64G。
首先切換到work賬號,然後執行一個測試程式就是建立執行緒,發現只能跑2900多個,我的筆記本還能跑2000多個呢,這顯然不對。然後在java –Xms2g 來執行,結果一樣,這就說明不是JVM記憶體不夠問題。肯定是哪裡有系統限制。
測試程式程式碼如下:

編譯程式碼,它會在當前目錄生成一個類檔案,名稱就是你定義的第一個類

java 類名 即可
java –Xms2g 類名 指定記憶體大小

無論你是否設定2g記憶體,執行執行緒數量都上不去,這就說明不是JVM記憶體不過,因為執行緒消耗的是系統記憶體,那麼系統記憶體充裕但是依然建立失敗,所以一定是其他方面有問題。因為任何系統可以執行的執行緒數量都有有限的。在JAVA語言裡,你建立一個執行緒,會在JVM記憶體建立一個記憶體物件的同時建立一個作業系統執行緒,而這個系統執行緒的記憶體不是使用JVM記憶體的,而是使用系統中剩下的記憶體建立的。也就是你給JVM記憶體越多,那麼你能建立的執行緒數就越少,也就是越容易發生這個異常。

(MaxProcessMemory – JVMMemory – ResverdOsMemory)/ ThreadStackSizk = 執行緒數量
MaxProcessMemory:一個程式最多使用的記憶體大小
JVMMemory:JVM的Heap大小,也就是堆,因為你也只能設定堆的大小,這個的值是堆的最小值+PermGen的大小
ResverdOsMemory:作業系統保留的記憶體,也就是核心使用的,一般為120M。
ThreadStackSizk:執行緒棧的大小,單位為位元組byte 所以上面的公式要統一換成位元組來計算。系統有64G記憶體,就算不適用公式計算也應該知道記憶體是絕對夠用的。那到底是什麼問題?

懷疑到了系統限制,透過使用ulimit –u來檢視最大程式數量是16384,這顯然也很大,因為透過pstree –p | wc –l 進行統計,當前系統才幾千個執行緒,離16384還很遠。但是我是在root賬號下執行的ulimt –u,突然想到不同賬號會不會有不同限制,果斷切換到work賬號,執行ulimit –u發現結果是4096,而這時再執行pstree –p | wc –l 統計結果為3800多個。也就是說work賬號可用執行緒數量不太多了。這也就是為什麼在work賬號下執行測試程式即使你更改了JVM引數結果還是一樣的。這時候就基本定位到問題,然後修改系統引數

然後再次切換到work賬號執行程式,發現可以跑到1.5萬,執行緒明顯上升。這時候再執行ulimt –u發現值和root賬號的結果是一樣的。

需要注意在/etc/secriyt/limits.cof中也可以設定這個,但是如果你在limits.d下面的配置檔案也設定了同樣的名稱只是值不同,那麼它會取2個值中最小的。
重點:雖然對work和root設定了unlimited,但是也不意味著可以無限增長,因為這個最大可用量是系統決定的,它的具體值受到實體記憶體頁個數等限制。
解決上面問題的通常辦法是:

  • 使用64位系統,這樣意味著可以使用更多實體記憶體
  • 減少給JVM分配的記憶體,這樣就有更多可用實體記憶體給執行緒,不過現在伺服器級別都是64G記憶體起,這個JVM記憶體也可以不設定或者給它最多2G大小,這個取決於具體你物理機跑多少個JVM例項所決定。
  • 減少單個執行緒的棧大小(-Xss 引數設定)

如果上面的辦法不行,你就需要考慮系統限制了
說明:當某一賬號所能執行的執行緒跑滿了之後,你就無法執行任何系統命令了,它會重試幾次然後最終失敗,這是系統執行緒跑滿的一個最明顯訊號。

3. 關於系統引數說明

檢視當前系統執行的匯流排程數量

top -H
# 也可以透過下面的命令檢視
pstree -p | wc -l

3.1 PID_MAX

系統允許的最大PID值,也就是最多有多少個程式或者執行緒(因為一個執行緒也佔用一個PID)。因為0-299是系統使用,所以最大值減去300就是使用者可用的數量。這個值由系統自動生成的。最大可以是32767,但是其實新系統都沒有這個限制,這個32767主要是為了和老版本的Linux和Unix相容,如果你不考慮先後相容問題,可以設定的更大,不過太大也沒有什麼意義。

這個值由2個數決定,其中一個是PIDS_PER_CPU_DEFAULT值為1024,另外一個是和當前伺服器CPU個數有關。
這個PID是程式號,但是Linux中執行緒也佔用一個號。如下圖:

所以如果這個可用程式號範圍太小也會出現無法建立執行緒的情況,不過一般不會因為這個原因。如何修改呢?

# 臨時修改
echo NUMBER > /proc/sys/kernel/pid_max

# 永久修改 修改該檔案/etc/sysctl.conf,增加如下內容
kernel.pid_max=NUMBER

3.2 thread-max

系統允許的最大執行緒數量

這個值最主要受到實體記憶體限制,這個值是這麼算出來的:

# mempages 是實體記憶體大小
# THREAD_SIZE 就是棧大小,透過ulimit -s可以看到,預設是8M
# PAGE_SIZE 記憶體頁大小   透過 getconf PAGESIZE 檢視
max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE)

3.3. Max user processes

最大使用者進數量,也就是某個使用者最多可以執行多少個程式或者執行緒

# CentOS 7是下面的檔案
/etc/security/limits/d/20-nproc.conf
# CentOS 6是下面的檔案
/etc/security/limits.d/90-nproc.conf

不同使用者可以執行的執行緒數量也不同,就算你的記憶體很大,可用PID很多,系統允許的執行緒數量也非常多,但是你執行程式的賬號如果被設定了很小的值也是不能超過這個值的。這個值可以計算:

這2個數應該很相近,但是我這裡差別很大,是因為在/etc/security/limit.conf中設定了限制為16384,所以即便你在limit.d的20-nproc.conf針對某個使用者設定了最大值也不生效,這兩個檔案如果設定了相同的內容則以最小值為準。看下圖:

注意:ulimit -u NUMBER這個設定僅對當前shell有效,當你再開啟一個shell時其值依然是之前的,以及你退出當前shell再次進入則又會恢復到之前的設定。

該檔案的格式是:

USER TYPE RESOURCE NUMBER

USER:具體使用者或者*表示所有使用者
TYPE:soft表示當前系統生效的值、hard表示所能設定的最大值,soft不能大於hard的值,用“-”表示同時對soft和hard設定值

RESOURCE 說明
core 限定核心檔案的大小
data 最大資料大小
fsize 最大檔案大小
memlock 最大鎖空間大小
nofile 單個程式最大開啟檔案數量
rss 最大持久設定
statck 最大棧大小
cpu 以分鐘為單位的CPU時間
nproc 最大程式(含執行緒)數量
as 地址空間限制
maxlogins 某使用者裕興登入的最大條目

 

 

 

 

 

 

 

 

 

3.4 stack size

系統執行緒棧大小

這個是系統建立一個執行緒所消耗的記憶體大小,8M=8192kb。理論上最大執行緒數量應該小於實體記憶體除以8M(畢竟程式碼段、資料段也要佔用記憶體以及管理執行緒的執行緒等)。如果你要想增大執行緒數量可以把棧大小調整小一點。

3.5 總結

一個JVM可以建立多少執行緒,首先由JVM設定決定(-Xms,-Xmx,-Xss),另外受到外部因素影響,就是系統設定(最大PID、最大執行緒、棧記憶體大小、最重要的還是實體記憶體由多少)、其二就是使用者設定(使用者可以執行多少個程式或執行緒),綜合上述因素的最小值就是一個JVM可以建立多少執行緒。那麼如何快速知道當前系統允許的最大程式或執行緒數量呢?

PID_MAX和thread_max這兩個一般不用關心,很少有人去修改這個東西,而且就算改也會改大。另外要注意你當前的登入賬戶或者說執行程式的賬戶,這個就要去/etc/security/limit.conf和/etc/seurity/limits.d/20-nproc.conf裡檢視指定使用者的最大程式數量也就是ulimit可以看到的,兩個檔案取最小值。如果最大程式數量不是問題,配置檔案也沒有問題,那你就要看看 /proc/sys/vm/max_map_count 這個值,如果也很大但是執行緒數量還是上不去你就要考慮是不是記憶體太小導致的。

4. 排查過程中使用到的命令

# 找到最消耗資源的執行緒
top –Hp `pgrep –u work java`
# 檢視最消耗資源的執行緒在幹什麼
jstack -l PID > /tmp/a.txt
# 檢視程式中執行緒的情況
top –H PID  
# 檢視該程式裡面有多少執行緒
top –H pid | wc –l 
# 檢視系統目前執行的執行緒或程式總數
pstree –p | wc –l 

 

相關文章