应直接跟踪 jvm 进程 pid,而非 java 命令;因 java -jar 仅是启动器,真正执行 i/o 的是后续 jvm 进程,用 strace java -jar 只能捕获启动瞬间调用,漏掉日志写入、配置读取等关键磁盘 i/o。

直接跟踪 Java 进程的 PID,而不是 java 命令本身。Java 启动后,java -jar app.jar 只是启动器,真正运行的是 JVM 进程(含 GC 线程、JIT 编译线程等)。用 strace java -jar... 只能捕获极短的启动阶段调用,后续大量日志写入、配置读取、临时文件操作等关键 I/O 完全漏掉。
定位 Java 进程 PID 并挂载 strace
先查真实 PID:
-
jps -l—— 显示所有 Java 进程主类全名及 PID(推荐,轻量且准确) -
pgrep -f "app.jar"或pidof java(需确认无其他 Java 进程干扰)
再用 strace 实时挂载:
strace -p <pid> -f -s 256 -ttt -T -e trace=open,openat,read,write,fsync,close</pid>-
-f:必须加,JVM 多线程 + 可能调用
Runtime.exec()fork 子进程,不加会丢失子进程 I/O - -s 256:防止路径、错误信息被截断(默认仅 32 字符)
-
-ttt:输出 Unix 时间戳(微秒级),便于与
iostat -x 1的await对齐 -
-T:显示每次系统调用耗时,重点关注
write和fsync是否超 50ms - -e trace=...:聚焦磁盘 I/O 核心调用,过滤掉 mmap、brk、clock_gettime 等干扰项
识别文件描述符泄漏的关键信号
FD 泄漏不是静态快照问题,而是随时间持续增长的行为:
- 执行
ls /proc/<pid>/fd/ | wc -l</pid>记下初始值 - 运行
watch -n 1 'ls /proc/<pid>/fd/ | wc -l'</pid>,观察数字是否稳定上升(watch 自动高亮变化) - 若每秒 +1 或跳变,基本确认泄漏存在
进一步分析类型分布:
-
lsof -p <pid> | grep socket | wc -l</pid>—— socket 数远超业务并发量?说明连接未 close -
lsof -p <pid> | grep anon_inode</pid>—— 大量anon_inode:[eventpoll]?epoll 实例或 timerfd 未释放 - 特别注意 NAME 列含
(deleted)的条目:文件已被rm,但进程仍持 fd,常见于日志轮转后未 reload
交叉验证:strace + lsof + iostat 锁定根因
单看 strace 容易误判。例如 write(12, "...", 4096) = 4096 耗时 230ms,不等于磁盘慢——可能是脏页回写阻塞、日志框架批量 flush、或 NFS 云盘延迟。
-
iostat -x 1:看目标磁盘
await是否持续 >20ms、%util是否接近 100% -
lsof -p
:将 strace 中高频出现的 fd(如 fd=12)映射到真实路径,确认是否为 /var/log/app/info.log或/tmp/upload-xxx -
结合 fd 状态:若 lsof 输出中大量
REG类型文件处于DEL(deleted)状态,说明句柄泄漏已加剧 fsync 压力
Java 特有注意事项
Java 的资源管理比 C 更隐蔽:
-
try-with-resources只保证AutoCloseable接口实现类的关闭,对 JNI 创建的 native socket、file descriptor 不生效 - 底层 native 资源(如 Netty 的 epoll fd、自定义 JNI 文件操作)必须在
Cleaner或finalize中显式 close,否则必然泄漏 - JVM 自身也会打开大量 fd(如
/dev/random、/proc/self/fd/、JFR 日志),需结合lsof -p <pid> -a -d</pid>按类型过滤,避免混淆
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











