Java应用运行在JVM之上,很多文件磁盘I/O操作最终都会通过JVM调用系统层面的接口完成,当应用出现文件读写慢、I/O阻塞等问题时,直接分析Java代码往往难以找到根因,这时候可以通过strace跟踪Java进程的系统调用来定位问题。

strace工具基本说明
strace是Linux系统下的系统调用跟踪工具,能够拦截并记录进程发起的所有系统调用,包括调用的参数、返回值、耗时等信息。对于磁盘I/O相关的分析,我们主要关注open、read、write、close、stat等和文件操作相关的系统调用。
安装strace
大部分Linux发行版默认已经安装了strace,如果没有可以通过包管理器安装:
- CentOS/RHEL系统:
yum install strace -y - Ubuntu/Debian系统:
apt-get install strace -y
获取Java进程PID
在使用strace跟踪Java进程之前,需要先获取目标Java进程的PID,可以通过jps命令或者ps命令查询:
# 使用jps命令查看所有Java进程 jps -l # 使用ps命令过滤Java进程 ps -ef | grep java
假设我们获取到的目标Java进程PID为12345,后续所有跟踪操作都会基于这个PID展开。
使用strace跟踪Java进程系统调用
基础跟踪命令
如果要对正在运行的Java进程进行跟踪,可以使用-p参数指定PID:
# 跟踪PID为12345的Java进程的所有系统调用 strace -p 12345
不过这个命令会输出所有系统调用,内容非常多,我们需要过滤出和磁盘I/O相关的调用,可以通过-e参数指定要跟踪的系统调用类型:
# 只跟踪文件相关的系统调用 strace -p 12345 -e trace=file # 跟踪读写相关的系统调用 strace -p 12345 -e trace=read,write
记录调用耗时
要定位性能瓶颈,调用耗时是非常关键的指标,可以通过-T参数让strace输出每个系统调用的耗时:
# 跟踪文件调用并输出每个调用的耗时 strace -p 12345 -e trace=file -T
输出内容中,每个系统调用末尾的<0.000123>格式的内容就是该调用的耗时,单位是秒。
输出到文件方便分析
系统调用输出内容较多,建议重定向到文件中后续分析:
# 将跟踪结果输出到strace_java.log文件中 strace -p 12345 -e trace=file,read,write -T -o strace_java.log
分析跟踪结果定位I/O瓶颈
拿到跟踪日志后,我们可以从以下几个维度分析磁盘I/O的性能问题:
1. 查看高耗时调用
搜索日志中耗时数值较大的调用,比如耗时超过0.1秒的read或者write调用,这些调用就是可能的瓶颈点。例如下面的日志表示一次write调用耗时0.2秒:
write(3, "some data content", 12) = 12 <0.200123>
2. 查看文件操作错误
如果系统调用返回错误,会在返回值位置显示错误码,比如-1 ENOENT (No such file or directory)表示文件不存在,频繁的打开文件错误也会导致I/O性能问题:
open("/data/test.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = -1 ENOENT (No such file or directory) <0.000045>
3. 统计调用频率
可以统计某个系统调用的出现次数,如果open调用频率过高,说明可能存在没有复用文件句柄的问题,频繁打开关闭文件会带来额外的I/O开销:
# 统计open调用的次数
grep -c "open(" strace_java.log
4. 关联文件描述符和文件路径
系统调用中的第一个参数通常是文件描述符,我们可以通过open调用的返回值找到文件描述符对应的文件路径,例如下面的open调用返回文件描述符3,后续所有操作描述符3的调用都是操作这个文件:
open("/data/app.log", O_WRONLY|O_CREAT|O_APPEND, 0666) = 3 <0.000032>
write(3, "log content", 11) = 11 <0.000021>
注意事项
- strace跟踪会对进程性能产生一定影响,生产环境使用时要控制跟踪时间,避免影响业务正常运行。
- 如果Java进程是通过容器运行的,需要在容器内部执行strace,或者宿主机上找到容器对应的进程PID进行跟踪。
- 部分JVM的I/O操作可能通过异步方式处理,跟踪时可能需要结合
-f参数跟踪子进程的系统调用,避免遗漏。
示例:完整跟踪分析流程
假设我们有一个Java应用频繁写文件慢,完整分析流程如下:
# 1. 获取Java进程PID
jps -l
# 假设输出为 12345 com.test.FileWriteApp
# 2. 跟踪该进程的文件和读写调用,输出耗时并记录到日志
strace -p 12345 -e trace=file,read,write -T -o strace_result.log
# 3. 等待30秒后停止跟踪,分析日志
# 搜索耗时超过0.1秒的调用
grep -E "<0.[1-9][0-9]*>" strace_result.log
# 4. 找到高耗时的write调用对应的文件描述符,回溯open调用找到文件路径
grep "open(" strace_result.log | grep "= 3"
通过以上流程就可以定位到具体是哪个文件的写入操作耗时过高,进而结合业务代码分析是磁盘本身性能问题,还是写入逻辑不合理导致的瓶颈。