在使用 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
只检查某个进程的最大数(
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
解决方法
- 通过命令增大允许打开的文件数
ulimit -n 2048
这样就可以把当前用户的最大允许打开文件数量设置为2048了,但这种设置方法在重启后会还原为默认值。
ulimit -n 命令非root用户只能设置到4096。 想要设置到更大需要sudo权限或者root用户。
- 修改系统配置文件
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)) {
// 文件操作
}