StackTraces
Java已验证

Java Too many open files 文件描述符耗尽

@debug_master32 天前更新阅读 6 min00

这个报错是 OS 层面的,不是 Java 代码 bug。Linux 把文件、socket、管道都算作文件描述符(FD),进程能同时打开的 FD 有上限。报错不代表你的代码写错了,而是「句柄开得太多或没及时关」,最常见的陷阱则是一个很多人都踩过的:改了 ulimit 却对正在跑的服务根本没生效。

Issue & Env(案发现场与环境)

  • 现象:高并发服务运行一段时间后拒绝新连接,日志出现 Too many open files,重启后暂时恢复,隔一阵又复发。
  • 环境:Linux,Java 服务,常经 systemd、Docker 或云平台部署。

StackTrace(堆栈日志)

Java 侧常见的两种形态:

Caused by: java.io.IOException: Too many open files
	at java.io.UnixFileSystem.createFileExclusively(Native Method)
	at java.io.File.createNewFile(File.java:1006)
java.net.SocketException: Too many open files
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
    at java.net.ServerSocket.implAccept(ServerSocket.java:545)
    at java.net.ServerSocket.accept(ServerSocket.java:513)
    at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:455)
    at java.lang.Thread.run(Thread.java:748)

SocketException 说明是连接层面被打满,IOException 说明是文件创建/读写层面。

Reproduce(复现代码)

循环打开文件或 socket 却不关闭,FD 只增不减:

public static void main(String[] args) throws Exception {
    List<Socket> sockets = new ArrayList<>();
    for (int i = 0; ; i++) {
        sockets.add(new Socket("localhost", 8080)); // 每次开一个,从不关闭
    }
}

跑上一阵就会撞上进程的 FD 上限,抛 Too many open files。

Root Cause(根因分析)

Linux 对 FD 有三层限制,任何一层被击穿都会报这个错:

  1. 单进程软限制(RLIMIT_NOFILE soft):ulimit -n 看到的值,实际生效的就是它,很多发行版默认只有 1024。
  2. 单进程硬限制(hard):软限制能自己抬到的上限,非特权用户只能往下调。
  3. 系统级 fs.file-max:全系统所有进程 FD 总和的上限,对应 errno 23(ENFILE),报错信息会写成 Too many open files in system。

最大的坑在「改了不生效」。ulimit -n 65536 只是改了当前 shell 的值,跟那个几天前就已经启动的服务毫无关系;limits.conf 又只管登录会话,systemd 管理的服务根本不理它。于是出现「我在 shell 里看是 10 万,跑起来还是 1024」的矛盾。

另一个陷阱是统计口径:lsof -p PID | wc -l 会把工作目录、可执行文件、内存映射库也算进去,数值虚高,真正权威的是 /proc/PID/fd 里实际打开的 FD 数。

代码层面,泄漏基本来自三类:打开的文件/流没关、Socket/连接池的连接没归还、FileChannel 或 RandomAccessFile 没释放。lsof -p PID | grep -c socket 能快速判断是文件句柄还是连接在疯涨。

The Fix(解决方案)

先看真正生效的限制和实际用量:

# 该进程真正生效的 soft/hard 限制
cat /proc/<PID>/limits | grep "open files"
# 实际已打开的 FD 数(最准确)
ls /proc/<PID>/fd | wc -l
# 全系统水位
cat /proc/sys/fs/file-nr

systemd 服务要在单元里改,而不是 limits.conf:

[Service]
LimitNOFILE=65535

然后 systemctl daemon-reload && systemctl restart <服务>。Docker 容器要显式传:docker run --ulimit nofile=65536:65536 ...。

是 FD 泄漏就回到代码,用 try-with-resources 保证流和 channel 关闭,连接池要保证 close() 归还连接,别留着 Socket 到堆里等 GC——GC 回收对象慢,FD 打满会比内存打满更早发生。查泄漏用 lsof -p PID 按文件类型统计,能一眼看出是哪种句柄在涨。

limits 合理、也没泄漏,才考虑是并发确实很高,那就把 soft 限制按连接数加上余量调大,注意单进程 soft 不能超过 fs.nr_open,否则设不上去。

References(参考来源)

最后更新于 2026-09-09

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法