第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 aceiling 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限制,操作會被拒絕。 2. 一個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的修改影響。 3. 增加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說明參考:https://www.kernel.org/doc/Documentation/sysctl/fs.txt)。要查看一個進程的打開文件數,直接查看目錄/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
- sof | 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?
本周刊示例代碼與文檔markdown文本統一整理於github:
maoshuai/java-linux-weekly
本周比較忙,無奈先拿了我之前一篇筆記整理整理充了個數😓,見諒了;原載於:Too many open files以及ulimit的用法