Java IOException “Too many open files”

在使用 Java 操作文件时,可能会出现一个异常 IOException,并且附带描述“打开的文件太多” Too many open files。

基础回顾

JVM 在处理文件时,会将文件管理这种操作交给操作系统处理,以达到较好的隔离效果。

这样以来,Java程序中每打开一个文件,操作系统都会分配一个文件描述符file descriptors 与当前进程相关联。一旦 JVM 处理完文件,它就会释放文件。

Too many open files 在 Linux 系统中比较常见,Linux 在操作文件、通讯链接(socket 等)甚至端口监听等行为时,都会记录在一个整数值中,这个值一般被称为句柄。

Too many open files 就是指在某一刻的句柄数超过了操作系统限制。

通过命令 ulimit -a 可以查看操作系统的最大句柄数。

原因分析

既然知道了操作系统会为每个被 Java 程序打开的文件分配文件句柄,我们来模拟一个场景:无限地打开文件,直到操作系统将文件句柄消耗完。

try {
    for (int i = 0; i < 1000000; i++) {
        FileInputStream stream = new FileInputStream(file);
    }
} catch (Exception e) {
    e.printStackTrace();
}

当我们无限制地打开文件以后,操作系统最终用完了文件描述符,它会将这种情况转发给 JVM,之后程序抛出IOException。

处理方式

既然操作系统的文件句柄有最大限制,那就来加大这个限制的数量。

扩容

命令 ulimit -a 查看

[mapull@VM-0-12-centos ~]$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 31203
max locked memory       (kbytes, -l) 64
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) 31203
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

open files 那一行就代表系统目前允许单个进程打开的最大句柄数,这里是1024。

也可以通过 ulimit -n 直接返回一个数字:

[mapull@VM-0-12-centos ~]$ ulimit -n
1024

只检查某个进程的最大数( 请替换为真实PID):

cat /proc/<pid>/limits

[mapull@VM-0-12-centos ~]$ cat /proc/956/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             31203                31203                processes 
Max open files            1024                 4096                 files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       31203                31203                signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us 

解决方法

  1. 通过命令增大允许打开的文件数

ulimit -n 2048

这样就可以把当前用户的最大允许打开文件数量设置为2048了,但这种设置方法在重启后会还原为默认值。

ulimit -n 命令非root用户只能设置到4096。 想要设置到更大需要sudo权限或者root用户。

  1. 修改系统配置文件

vim /etc/security/limits.conf

在最后加入

* soft nofile 4096
* hard nofile 4096

或者只加入

* - nofile 8192

最前的 * 表示所有用户,- 表示同时设置 soft、hard。

可根据需要设置某一用户,例如:

mapull soft nofile 8192
mapull hard nofile 8192

修改完成后,可以通过su - mapull 切换用户,或者重新登录,来使该配置生效

如果修改完依旧无法解决问题,可以通过 lsof -p <pid> > openfiles.log 获取当前进程的所有句柄数据,然后分析应该设置的数量。

代码释放

上面通过操作系统增加最大文件打开数量,但要是程序不合适使用文件句柄,无论该数量改到多少,依旧会出现Too many open files。我们应该在使用完文件以后,关闭连接,销毁引用。让代码中的文件引用保持在合理数量范围。

for (int x = 0; x < 1000000; x++) {
    FileInputStream files = null;
    try {
            files = new FileInputStream(tempFile);
        } finally {
            if (files != null) {
                files.close();
            }
        }
}

一个正确的做法是,在 finally 块中执行关闭任务,确保总是可以正确地关闭文件的引用。

另一种更加优美的写法是利用 try-with-resources ,它允许我们通过在try定义中包含资源来自行销毁资源:

try (FileInputStream files = new FileInputStream(tempFile)) {
    // 文件操作
}
转载请注明出处:码谱记录 » Java IOException “Too many open files”
标签: