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

相关文章
相关标签/搜索