第3期:Too many open files以及ulimit的探討

Too many open files是Java常見的異常,一般是因爲系統配置或程序打開過多文件致使。這個問題經常又與ulimit的使用相關。關於ulimit的用法有很多坑,本文將遇到的坑予以梳理。html

Too many open files異常

下面是Java在系統超過最大打開文件數時的異常堆棧: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自己也有很多坑,下面作一下總結。linux

什麼是ulimit

直接參考ulimit的幫助文檔(注意:不是man ulimit,而是help ulimit,ulimit是內置命令,前者提供的是C語言的ulimit幫助):git

Modify shell resource limits.github

Provides control over the resources available to the shell and processes it creates, on systems that allow such control.shell

能夠看出,ulimit提供了對shell(或shell建立的進程)可用資源的管理。除了打開文件數以外,可管理的資源有: 最大寫入文件大小、最大堆棧大小、core dump文件大小、cpu時間限制、最大虛擬內存大小等等,help ulimit會列出每一個option限制的資源。或者查看ulimit -a也能夠看出:bash

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,但咱們可經過下面的方法程序驗證:dom

下面經過一段java程序,打開800個文件:eclipse

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知乎掘金

相關文章
相關標籤/搜索