步骤四:用 lsof 定位占用来源
查看 Java 进程打开的 TCP 连接:
lsof -p 12345 | grep -i tcp | head
示例输出:
java 12345 app 5u IPv6 ... TCP *:80 (LISTEN)
java 12345 app 6u IPv6 ... TCP localhost:80->localhost:41198 (CLOSE_WAIT)
java 12345 app 7u IPv6 ... TCP localhost:80->localhost:41204 (CLOSE_WAIT)
java 12345 app 9u IPv6 ... TCP localhost:80->localhost:41212 (CLOSE_WAIT)
如果存在大量长时间堆积的 CLOSE_WAIT,重点排查程序是否没有关闭 Socket。
CLOSE_WAIT 表示对端已关闭连接,本地程序还没关闭 Socket。大量连接持续停留在这个状态,重点排查 Socket 是否遗漏关闭。
TCP 连接不多时,直接执行 lsof -p 12345,检查日志文件、管道等其他占用。
步骤五:先保留关键证据
调整上限前,保留报错日志、FD 数量与上限、异常连接明细。
例如:
grep -i 'open files' /proc/12345/limits > fd-limits.txt
ls /proc/12345/fd | wc -l > fd-count.txt
lsof -p 12345 | grep -i tcp > tcp.txt
日志保存报错片段即可。交给研发前处理敏感信息。
步骤六:临时调高进程 FD 上限
FD 到达软限制、硬限制还有余量时,可以临时提高该进程的软限制,恢复新连接处理能力。
假设软限制是 256、硬限制是 1048576,可以执行:
prlimit -p 12345 -n 1024:1048576
再确认:
grep -i 'open files' /proc/12345/limits
执行后软限制应变为 1024。硬限制按现场查询结果填写;操作其他用户的进程需要相应权限。
prlimit 只调整目标进程的限制。已泄漏的连接不会自动释放。
提高上限后仍无法恢复时,评估重启服务或切换流量。
步骤七:验证接口恢复,再交给研发
对实际业务接口重新发起请求,例如:
接口返回正常后,再看错误日志和 FD 数量是否继续增长。
交接研发时明确三件事:
- Java 进程 FD 达到软限制,日志报
Too many open files。
- 大量 TCP 连接长期处于
CLOSE_WAIT,怀疑连接未释放。
- 已临时提高 FD 上限并恢复服务,需要研发检查连接关闭逻辑,防止再次耗尽。
临时调高上限后,研发仍需修复未关闭的连接。
03 典型生产故障案例
案例一:大量 CLOSE_WAIT 堆积,接口开始报错
故障现象
Java 进程还在,HTTP 接口大量失败,日志持续出现 Too many open files。
排查过程
jps -l
ls /proc/12345/fd | wc -l
grep -i 'open files' /proc/12345/limits
lsof -p 12345 | grep -i tcp | head
发现 FD 占用达到软限制,lsof 中存在大量持续不消失的 CLOSE_WAIT。
原因与处理
连接已经被对端关闭,但 Java 程序没有及时释放对应的 Socket,FD 持续堆积。保留证据后评估临时调高上限,验证接口恢复,并把连接关闭问题交给研发。
处理:先恢复接口,再由研发修复 Socket 关闭逻辑。
案例二:FD 用满,但 TCP 连接并不多
故障现象
服务同样出现 Too many open files,但 lsof 没有发现大量 TCP 连接。
排查方向
ls /proc/12345/fd | wc -l
lsof -p 12345 | head -n 30
继续检查进程打开的日志、普通文件、管道等资源;必要时统计各类 FD 的数量。
原因与处理
可能是程序重复打开文件流后没有关闭,例如读取文件、生成报表或处理上传时遗漏资源释放。先按业务情况止损,再让研发定位具体资源创建与关闭逻辑。
处理:按 lsof 结果确定是文件流还是网络连接占用。
案例三:调高上限后恢复,过一段时间又报错
故障现象
把软限制从 256 提高到 1024 后接口暂时恢复,但几个小时后又出现同样的报错。
排查方向
对比不同时间的 FD 数量、CLOSE_WAIT 数量和错误日志,确认占用是否持续增长。
原因与处理
提高 FD 上限只是扩大了可用名额,没有修复泄漏。应由研发排查 Socket、HTTP 客户端或连接池关闭逻辑,同时根据增长速度安排风险处置。
处理:持续观察 FD 增长,安排研发修复泄漏。
04 生产排障命令速查表
以下 PID、日志路径和端口均为示例,按现场替换。
| 排查目标 | 命令 | 看什么 |
|---|
| 查看报错日志 | tail app.log | Too many open files |
| 找 Java 进程 | jps -l | PID、JAR 名称 |
| JDK 工具不可用 | ps -ef | grep '[j]ava' | 对应 Java 进程 |
| 当前 FD 数量 | ls /proc/12345/fd | wc -l | 是否持续增长、接近上限 |
| FD 软硬限制 | grep -i 'open files' /proc/12345/limits | 第 1 个值是软限制,第 2 个是硬限制 |
| 进程所有 FD | lsof -p 12345 | 文件、网络连接、管道等 |
| TCP 连接 | lsof -p 12345 | grep -i tcp | head | 大量异常连接 |
| CLOSE_WAIT 数量 | lsof -p 12345 | grep -c CLOSE_WAIT | 是否持续堆积 |
| 查看进程当前限制 | prlimit -p 12345 -n | 当前 NOFILE 软硬限制 |
| 临时提高软限制 | prlimit -p 12345 -n 1024:1048576 | 按实际硬限制调整 |
| 验证 HTTP 接口 | curl localhost | 返回是否正常 |
最快排查路线:jps 找进程 → /proc/PID/fd 查占用 → /proc/PID/limits 查上限 → lsof 查原因。
05 面试应答参考话术
问题:Java 服务没退出,接口却报 Too many open files,你会怎么处理?
先确认业务进程还在运行,并查看错误日志。通过 jps -l 找到 PID,用 /proc/PID/fd 统计当前文件描述符数量,再从 /proc/PID/limits 查看软硬限制。
如果 FD 接近软限制,我会用 lsof -p PID 看具体被什么占用。发现大量持续堆积的 CLOSE_WAIT,就重点怀疑程序没有正确关闭网络连接。
线上先保留日志和连接证据。如果硬限制允许,可以临时提高进程 FD 软限制,验证接口是否恢复;无法恢复时再按应急流程处理。最后把证据交给研发修复连接泄漏,并持续观察 FD 是否增长。
06 面试高频追问与应答预案
Q1:文件描述符是什么?
进程访问文件、网络 Socket、管道等资源时,Linux 使用 FD 来标识。FD 数量有进程级限制,耗尽后新资源可能打开失败。
Q2:软限制和硬限制有什么区别?
软限制是当前生效的上限;硬限制是普通进程能把软限制提高到的边界。修改硬限制或提高其他进程的限制可能需要额外权限。
Q3:为什么 Java 进程还在,接口却不能访问?
FD 耗尽后,Java 无法接收新连接或打开新文件,接口会报错,但进程仍然可以运行。
Q4:CLOSE_WAIT 是什么意思?
对端已经关闭连接,本地应用还没关闭 Socket。短暂出现正常;大量连接持续堆积,通常要检查应用是否及时释放连接。
Q5:看到大量 CLOSE_WAIT,能直接认定代码泄漏吗?
结合 FD 增长速度和错误日志判断。大量 CLOSE_WAIT 持续不消失,再排查连接关闭逻辑。
Q6:为什么 lsof 能查 TCP 连接?
Socket 会占用 FD,lsof 可以直接列出进程的 TCP 连接。
Q7:直接执行 ulimit -n 65535 能修复正在运行的 Java 服务吗?
ulimit 调整当前 Shell 及随后启动的子进程。已经运行的 Java 进程使用 prlimit 调整。
Q8:prlimit 提高上限后,已经泄漏的连接会自动关闭吗?
不会。它增加可用 FD 数量,泄漏的连接仍然占用原来的 FD。
Q9:prlimit 调整后,服务重启还会保留吗?
通常不会。新进程从 Shell、systemd 或容器运行环境继承限制,长期生效要修改启动配置。
Q10:什么时候不建议直接调高 FD 上限?
进程已不稳定、资源持续快速增长或主机整体资源紧张时,调高上限可能放大影响。应先评估业务与主机资源,再决定是否切流或重启。
Q11:线上恢复后怎么判断根因是否解决?
在相同请求量下,检查 FD 是否稳定、CLOSE_WAIT 是否消退、接口是否继续报错。
Q12:运维最终要交给研发什么?
故障时间、Java PID、错误日志、FD 占用与限制、lsof 异常连接信息,以及临时止损操作和业务验证结果。
07 文件描述符一键检查脚本(只读)
适合已经找到 Java PID,需要快速确认 FD 是否接近上限、CLOSE_WAIT 是否堆积的场景。
保存为 check_java_fd.sh:
#!/usr/bin/env bash
# 用法:bash check_java_fd.sh PID
set -eu
pid=${1:-}
if [[ ! "$pid" =~ ^[0-9]+$ ]] || [[ ! -d "/proc/$pid/fd" ]]; then
echo '用法: bash check_java_fd.sh 有效PID' >&2
exit 1
fi
limit=$(awk '$1=="Max" && $2=="open" && $3=="files" {print $4, $5}' "/proc/$pid/limits")
read -r soft hard <<< "$limit"
count=$(ls -1 "/proc/$pid/fd" 2>/dev/null | wc -l)
printf 'PID: %s\nFD使用: %s\n软限制: %s\n硬限制: %s\n' "$pid" "$count" "$soft" "$hard"
if [[ "$soft" =~ ^[0-9]+$ ]] && (( soft > 0 )); then
awk -v n="$count" -v max="$soft" 'BEGIN {printf "使用率: %.1f%%\n", n/max*100}'
fi
if command -v lsof >/dev/null 2>&1; then
close_wait=$(lsof -nP -a -p "$pid" -iTCP 2>/dev/null | grep -c CLOSE_WAIT || true)
printf 'CLOSE_WAIT: %s\n' "$close_wait"
else
echo '未安装 lsof,跳过连接状态检查'
fi
使用示例:
bash check_java_fd.sh 12345
输出示例:
PID: 12345
FD使用: 256
软限制: 256
硬限制: 1048576
使用率: 100.0%
CLOSE_WAIT: 245
脚本只读取 /proc 和 lsof 信息,不会调整限制、关闭连接或重启服务。需以能查看目标进程信息的用户执行。
08 现场注意事项与故障复现资料
- 查看 FD 上限、实际占用以及增长速度。
- 结合错误日志和
CLOSE_WAIT 数量判断连接是否堆积。
- 提高上限后继续观察 FD 增长,及时安排研发修复。
- 记录临时处理措施和后续负责人。
测试环境复现思路
在隔离的 Linux 测试机上,使用一个故意不关闭 Socket 的 Java HTTP 服务,给该服务单独设置较低 FD 上限,再用并发请求触发 Too many open files。
复现时应观察四项结果:
- 接口起初正常,持续请求后开始报错。
- 日志真实出现
Too many open files。
- 进程 FD 占用达到软限制,存在大量持续堆积的
CLOSE_WAIT。
- 临时提高软限制后,接口可恢复,但 FD 仍可能继续增长。
配套故障复现 JAR 作为独立附件提供,仅在隔离测试机使用。