Too many open files
是Java常见的异常,通常是由于系统配置或程序打开过多文件导致。这个问题常常又与 ulimit
的使用相关。关于 ulimit
的用法有不少坑,本文将遇到的坑予以梳理。
下面是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的帮助文档(注意:不是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,第一个疑问是限制的维度是什么。比如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
是也并不反映系统打开的文件数!(后续周刊补充)
理解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可以 被进程在使用前自己修改 。
知道ulimit很好,但更重要的是怎么修改,这是工作中常见的任务。
关于ulimit的生效,抓住几点即可:
下面给出两个案例:
可以直接在该进程启动脚本中,增加ulimit -nS 2048即可
显然,非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 复制代码
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
查看,看到打印出 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
统计打开文件数上非常不准确。主要原因是:
lsof
统计,必须使用精巧的过滤条件。更简单和准确的方法是,通过/proc目录查看。获取系统打开文件说,直接查看/proc/sys/file-nr,其中第一个数字就是打开的file数(file-nr说明参考: www.kernel.org/doc/Documen…
)。要查看一个进程的打开文件数,直接查看目录/proc/$pid/fd里的文件数即可: 在研究的过程中, 我发现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?
通过pstree,发现eclipse的java是通过gnome-shell启动的,而命令行是通过gnome-terminal启动的。其中gnome-terminal又是通过systemd --user启动的,而systemd --user似乎不读取/etc/security/limits.conf的值。这个坑的说明有机会再填吧。
除了ulimit控制外, /proc/sys/fs/file-max
这个文件控制了系统内核可以打开的全部文件总数。所以,即便是ulimit里nofile设置为ulimited,也还是受限的。