1. 程式人生 > 程式設計 >第3期:Too many open files以及ulimit的探討

第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的區別:

  1. 無論何時,soft總是小於等於hard
  2. 無論是超過了soft還是hard,操作都會被拒絕。結合第一點,這句話等價於:超過了soft限制,操作會被拒絕。
  3. 一個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的生效,抓住幾點即可:

  1. ulimit的值總是繼承父程式的設定。
  2. ulimit命令可修改當前shell程式的設定。這也說明,為了保證下次生效,修改的地方要具有永續性(至少相當於目標程式而言),比如.bashrc,或程式的啟動指令碼)
  3. 從第2點也可以推出,執行中的程式,不受ulimit的修改影響。
  4. 增加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
複製程式碼

參考


《Java與Linux學習週刊》每週五發布,同步更新於:Github知乎掘金