第3期:Too many open files以及ulimit的探討
Too many open files
是Java常見的異常,通常是由於系統配置或程式開啟過多檔案導致。這個問題常常又與ulimit
的使用相關。關於ulimit
的用法有不少坑,本文將遇到的坑予以梳理。
Too many open files異常
下面是Java在系統超過最大開啟檔案數時的異常堆疊:
Exception in thread "main" java.io.IOException: Too many open files
at java.io.UnixFileSystem.createFileExclusively(Native Method)
at java.io.File.createTempFile(File.java:2024 )
at java.io.File.createTempFile(File.java:2070)
at com.imshuai.wiki.ulimit.App.main(App.java:16)
複製程式碼
如果不是程式問題(程式問題,需要看為什麼開啟很多檔案,比如通過lsof),一般要通過ulimit調整開啟檔案數限制解決,但ulimit本身也有不少坑,下面做一下總結。
什麼是ulimit
直接參考ulimit的幫助檔案(注意:不是man ulimit,而是help ulimit,ulimit是內建命令,前者提供的是C語言的ulimit幫助):
Modify shell resource limits.
Provides control over the resources available to the shell and processes it creates,on systems that allow such control.
可以看出,ulimit提供了對shell(或shell建立的程式)可用資源的管理。除了開啟檔案數之外,可管理的資源有:
最大寫入檔案大小、最大堆疊大小、core dump檔案大小、cpu時間限制、最大虛擬記憶體大小等等,help ulimit會列出每個option限制的資源。或者檢視ulimit -a
也可以看出:
maoshuai@ms:~/ulimit_test$ ulimit -a
core file size (blocks,-c) 0
data seg size (kbytes,-d) unlimited
scheduling priority (-e) 0
file size (blocks,-f) 100
pending signals (-i) 15520
max locked memory (kbytes,-l) 16384
max memory size (kbytes,-m) unlimited
open files (-n) 1024
pipe size (512 bytes,-p) 8
POSIX message queues (bytes,-q) 819200
real-time priority (-r) 0
stack size (kbytes,-s) 8192
cpu time (seconds,-t) unlimited
max user processes (-u) 15520
virtual memory (kbytes,-v) unlimited
file locks (-x) unlimited
複製程式碼
理解ulimit
在使用ulimit之前,有幾個容易迷糊的點:
ulimit的管理的維度
理解ulimit,第一個疑問是限制的維度是什麼。比如nofile設定為1024,是指當前使用者總共只能開啟1024個檔案,還是單個shell會話程式只能開啟1024個檔案?** 實際上help ulimit裡已經說清楚了:process,但我們可通過下面的方法程式驗證:
下面通過一段java程式,開啟800個檔案:
class Ulimit{
public static void main( String[] args ) throws IOException,InterruptedException
{
List<FileInputStream> fileList = new ArrayList<FileInputStream>();
for(int i=0;i<800;i++) {
File temp = File.createTempFile("ulimit-test",".txt");
fileList.add(new FileInputStream(temp));
System.out.println("file_seq=" + i + " " + temp.getAbsolutePath());
}
// keep it running,so we can inspect it.
Thread.sleep(Integer.MAX_VALUE);
}
}
複製程式碼
我們將nofile設定為1024
ulimit -n 1024
複製程式碼
然後我們執行兩個程式例項:
nohup java Ulimit >a.log &
nohup java Ulimit >b.log &
複製程式碼
檢視日誌a.log和b.log,都建立了800個檔案,沒有報異常。
如果將ulimit 設定為700,重新測試,發現java程式在建立688個檔案時就報了Too many open files
異常(之所以不是700整,是因為java本身也會開啟一些檔案),
file_seq=688 /tmp/ulimit-test7270662509459661456.txt
Exception in thread "main" java.io.IOException: Too many open files
at java.io.UnixFileSystem.createFileExclusively(Native Method)
at java.io.File.createTempFile(File.java:2024)
at java.io.File.createTempFile(File.java:2070)
at Ulimit.main(Ulimit.java:12)
複製程式碼
雖然ulimit的u是user的意思,但事實證明,ulimit控制的維度是shell會話或shell建立的程式(至少對於nofile來說)。即:當前使用者開啟的檔案數,是可以遠遠超過nofile的值。
所以,通過lsof | wc -l
檢視系統開啟檔案數,來判斷是否開啟檔案數是否超了,是不正確的。另外,lsof | wc -l
是也並不反映系統開啟的檔案數!(後續週刊補充)
soft和hard的區分
理解ulimit第二個重要方面是soft和hard的區分,ulimit對資源的限制區分為soft和hard兩類,即同一個資源(如nofile)存在soft和hard兩個值。
在命令上,ulimit通過-S和-H來區分soft和hard。如果沒有指定-S或-H,在顯示值時指的是soft,而在設定的時候指的是同時設定soft和hard值。
但soft和hard的區別是什麼是什麼呢?下面這段解釋較為準確(來自man 2 getrlimit )
The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may set only its soft limit to a value in the range from 0 up to the hard limit,and** (irre‐versibly) **lower its hard limit. A privileged process (under Linux: one with the CAP_SYS_RESOURCE capability) may make arbitrary changes to either limit value.
歸納soft和hard的區別:
- 無論何時,soft總是小於等於hard
- 無論是超過了soft還是hard,操作都會被拒絕。結合第一點,這句話等價於:超過了soft限制,操作會被拒絕。
- 一個process可以修改當前process的soft或hard。但修改需滿足規則:
- 修改後soft不能超過hard。也就是說soft增大時,不能超過hard;hard降低到比當前soft還小,那麼soft也會隨之降低。
- 非root或root程式都可以將soft可以在[0-hard]的範圍內任意增加或降低。
- 非root程式可以降低hard,但不能增加hard。即nofile原來是1000,修改為了900,在修改為1000是不可能的。(這是一個單向的,有去無回的操作)
- root程式可以任意修改hard值。
soft和hard在控制上其實並沒有區別,都會限制資源的使用,但soft可以被程式在使用前自己修改。
ulimit的修改與生效
知道ulimit很好,但更重要的是怎麼修改,這是工作中常見的任務。
關於ulimit的生效,抓住幾點即可:
- ulimit的值總是繼承父程式的設定。
- ulimit命令可修改當前shell程式的設定。這也說明,為了保證下次生效,修改的地方要具有永續性(至少相當於目標程式而言),比如.bashrc,或程式的啟動指令碼)
- 從第2點也可以推出,執行中的程式,不受ulimit的修改影響。
- 增加hard值,只能通過root完成
下面給出兩個案例:
案例1:某非root程式要求2048的nofile,經檢視當前soft為1024,hard為4096
可以直接在該程式啟動指令碼中,增加ulimit -nS 2048即可
案例2:某非root程式要求10240的nofile,經檢視當前soft為1024,hard為4096
顯然,非root使用者沒法突破。只能通過root修改,一般修改/etc/security/limits.conf
檔案,修改方法在該配置檔案中的註釋中也有說明,格式是:
一條記錄包含4️列,分別是範圍domain(即生效的範圍,可以是使用者名稱、group名或*代表所有非root使用者);t型別type:即soft、hard,或者-代表同時設定soft和hard;專案item,即ulimit中的資源控制專案,名字列舉可以參考檔案中的註釋;最後就是value。比如將所有非root使用者的nofile設定為100000
* hard nofile 10000
* soft nofile 10000
複製程式碼
執行中程式的limits的檢視
ulimit修改之後,可以直接通過ulimit命令檢視。對於已執行的程式,還有一種更準確的檢視方法(比如修改ulimit前就啟動的程式,如何知道其ulimit值就需要這種方法):檢視程式目錄下的limits檔案。比如,/proc/4660/limits檔案就記錄了4660號程式的所有limits值:
maoshuai@ms:~/ulimit_test$ cat /proc/4660/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 15520 15520 processes
Max open files 2000 2000 files
Max locked memory 16777216 16777216 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 15520 15520 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
複製程式碼
ulimit不加引數
曾經有小白直接用ulimit
檢視,看到打印出unlimited
,就認為開啟檔案不受限制。顯然這是不對的,help ulimit
中明確指出:
If no option is given,then -f is assumed.
所以,ulimit不加引數,相當於ulimit -f -S
(沒有指定-S或-H就相當於-S),實際上是指可寫入的檔案最大size。
其他
如何檢視系統開啟檔案數
losf命令雖然作用是"list open files",但用lsof | wc -l
統計開啟檔案數上非常不準確。主要原因是:
- 某些情況下,一行可能顯示的是執行緒,而不是程式,對於多執行緒的情況,就會誤以為一個檔案被重複開啟了很多次
- 子程式會共享file handler
如果用
lsof
統計,必須使用精巧的過濾條件。更簡單和準確的方法是,通過/proc目錄檢視。獲取系統開啟檔案說,直接檢視/proc/sys/file-nr,其中第一個數字就是開啟的file數(file-nr說明參考:www.kernel.org/doc/Documen…)。要檢視一個程式的開啟檔案數,直接檢視目錄/proc/$pid/fd裡的檔案數即可:
Java 自動將nofile的soft提升為hard上限
在研究的過程中,我發現java程式似乎不受nofile的soft值影響。檢視程式的limits檔案(/proc/$pid/limits),才發現nofile的soft被提升為和hard一樣。經過全網搜尋查詢,發現JDK的實現中,會直接將nofile的soft先改成了和hard一樣的值,可參考:How and when and where jvm change the max open files value of Linux?
Ubuntu中eclipse中啟動的java和命令列啟動的java,nofile不一樣
通過pstree,發現eclipse的java是通過gnome-shell啟動的,而命令列是通過gnome-terminal啟動的。其中gnome-terminal又是通過systemd --user啟動的,而systemd --user似乎不讀取/etc/security/limits.conf的值。這個坑的說明有機會再填吧。
file-max控制核心總共可以開啟的檔案數
除了ulimit控制外,/proc/sys/fs/file-max
這個檔案控制了系統核心可以開啟的全部檔案總數。所以,即便是ulimit裡nofile設定為ulimited,也還是受限的。
ulimit常用選項
ulimit -a # 檢視所有soft值
ulimit -Ha # 檢視所有hard值
ulimit -Hn # 檢視nofile的hard值
ulimit -Sn 1000 # 將nofile的soft值設定為1000
ulimit -n 1000 # 同時將nofiles的hard和soft值設定為1000
複製程式碼
參考
- Session failures with error "Too many open files"
- ulimit man page
lsof | wc -l
sums up a lot of duplicated entries- How and when and where jvm change the max open files value of Linux?
- Why file-nr and lsof count on open files differs?