Java 故障分析与性能调优命令详解(含案例)
一、JVM 进程与基础信息查询
1. jps - 定位 Java 进程
功能:快速列出当前系统中所有 Java 进程的 PID 和主类名,是后续所有命令的基础。语法:jps [选项]
,常用选项-l
显示主类完整路径,-v
显示 JVM 启动参数。案例:假设服务器运行着一个 Spring Boot 应用和一个普通 Java 程序,执行jps -l
后输出:
1234 org.springframework.boot.loader.JarLauncher # Spring Boot应用,PID=1234
5678 com.example.DemoApplication # 普通Java程序,PID=5678
7901 sun.tools.jps.Jps # jps自身进程
通过该命令可快速获取目标应用的 PID(如 1234),用于后续排查。
2. jinfo - 查看 JVM 配置
功能:查看指定 Java 进程的 JVM 参数(如堆大小、GC 算法)和系统属性。语法:
jinfo [PID]
:查看所有配置和属性。jinfo -flags [PID]
:仅查看 JVM 启动参数(关键)。jinfo -sysprops [PID]
:仅查看系统属性。
案例:排查 “堆内存溢出” 前兆时,先确认堆配置是否合理。执行jinfo -flags 1234
:
Attaching to process ID 1234, please wait...
Debugger attached successfully.
Server compiler detected.
VM Flags:
-XX:InitialHeapSize=536870912 # 初始堆大小512M
-XX:MaxHeapSize=1073741824 # 最大堆大小1G
-XX:MetaspaceSize=268435456 # 元空间初始大小256M
-XX:+UseG1GC # 使用G1垃圾收集器
-XX:+HeapDumpOnOutOfMemoryError # OOM时自动生成堆快照
若发现MaxHeapSize
过小(如仅 256M),且应用内存需求高,可判断堆配置不合理,需调整启动参数-Xmx
。
二、内存问题排查(OOM、内存泄漏)
1. jstat - 实时监控内存与 GC
功能:持续采集 JVM 内存区域(Eden、Survivor、Old、Metaspace)使用情况及 GC 统计,快速定位内存异常趋势。核心语法:jstat -[选项] [PID] [间隔时间(ms)] [采集次数]
常用选项:
-gc
:监控堆内存各区域使用量、GC 次数、GC 耗时。-gcutil
:以百分比显示堆内存使用情况,更直观。
案例:监控 Spring Boot 应用(PID=1234)的 GC 情况,每 1 秒采集 1 次,共 10 次:执行jstat -gcutil 1234 1000 10
,输出:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT0.00 50.00 95.23 88.67 92.10 89.32 125 6.234 8 15.678 21.9120.00 50.00 98.76 89.12 92.10 89.32 126 6.310 8 15.678 21.9880.00 0.00 3.45 90.05 92.10 89.32 127 6.385 9 16.890 23.275
关键指标解读:
S0/S1
:Survivor 区使用率,若频繁切换非 0,说明年轻代回收正常。E
:Eden 区使用率,若持续接近 100%,说明年轻代对象创建快,回收频繁。O
:老年代使用率,若持续增长至 90% 以上,可能面临 OOM 风险。FGC
:Full GC 次数,案例中 10 秒内 FGC 从 8 次增至 9 次,且FGCT
(Full GC 耗时)增加 1.2 秒,说明老年代回收压力大,可能存在内存泄漏或堆配置不足。
2. jmap + jhat - 堆快照分析
功能:
jmap
:生成堆转储文件(记录堆中所有对象的类型、数量、大小、引用关系),是排查内存泄漏的核心工具。jhat
:离线分析堆转储文件,通过 Web 界面展示对象分布。
语法:
- 生成堆快照:
jmap -dump:format=b,file=heapdump.hprof [PID]
(format=b
表示二进制格式)。 - 分析快照:
jhat heapdump.hprof
,启动 Web 服务(默认端口 7000),浏览器访问http://localhost:7000
。
案例:排查 “应用运行 2 天后 OOM” 问题
- 当应用老年代使用率达 90% 时,执行
jmap -dump:format=b,file=app_heap.hprof 1234
,生成堆快照。 - 执行
jhat app_heap.hprof
,浏览器打开后查看 “Show heap histogram”(堆直方图),发现com.example.UserSession
类的对象数量达 10 万 +,且每个对象占用 1KB 内存,总占用 100MB 以上。 - 进一步查看 “Find object by id”,追溯引用关系,发现
SessionManager
类中存在一个静态Map
,未及时移除过期的UserSession
对象,导致对象持续堆积到老年代,最终引发 OOM。解决方案:将Map
替换为带过期策略的ConcurrentHashMap
或使用Guava Cache
。
三、线程问题排查(死锁、线程阻塞)
1. jstack - 线程栈快照分析
功能:生成线程栈快照,展示每个线程的执行状态(RUNNABLE、BLOCKED、WAITING)、调用栈,用于排查死锁、线程阻塞、长时间运行的线程。语法:jstack [选项] [PID]
,常用选项-F
(强制生成快照,适用于进程无响应时)。
案例 1:排查死锁执行jstack 1234
,输出中若包含 “Found one Java-level deadlock”,则明确存在死锁:
Found one Java-level deadlock:
=============================
"Thread-1":waiting to lock monitor 0x00007f8a1203d000 (object 0x000000076b8e4260, a java.lang.Object),which is held by "Thread-0"
"Thread-0":waiting to lock monitor 0x00007f8a1203f800 (object 0x000000076b8e4270, a java.lang.Object),which is held by "Thread-1"
原因:Thread-0 持有锁 A,等待锁 B;Thread-1 持有锁 B,等待锁 A,形成循环等待。解决方案:统一线程获取锁的顺序(如先锁 A 再锁 B)。
案例 2:排查线程阻塞执行jstack 1234
,发现多个线程状态为BLOCKED
:
"http-nio-8080-exec-5" #23 daemon prio=5 os_prio=0 tid=0x00007f8a10032000 nid=0x5a waiting for monitor entry [0x00007f8a08a7e000]java.lang.Thread.State: BLOCKED (on object monitor)at com.example.OrderService.createOrder(OrderService.java:45)- waiting to lock <0x000000076b901000> (a com.example.OrderService)at com.example.OrderController.addOrder(OrderController.java:28)
分析:OrderService.createOrder
方法使用synchronized
修饰,且当前有线程长时间持有锁(如执行慢 SQL),导致后续请求线程阻塞。解决方案:优化锁内逻辑(如将慢 SQL 移至锁外),或改用ReentrantLock
设置超时时间。
2. top + jstack - 定位高 CPU 线程
场景:应用 CPU 使用率突然飙升至 100%,需找到具体耗 CPU 的线程和代码。步骤:
- 执行
top -Hp 1234
(-H
显示线程级 CPU 占用),找到 CPU 使用率最高的线程 PID(如 12345)。 - 将线程 PID 转换为 16 进制(因为 jstack 输出的线程 ID 是 16 进制):
printf "%x\n" 12345
,得到3039
。 - 执行
jstack 1234 | grep -A 20 3039
,查看该线程的调用栈:
"Thread-8" #35 prio=5 os_prio=0 tid=0x00007f8a10045000 nid=0x3039 runnable [0x00007f8a0857a000]java.lang.Thread.State: RUNNABLEat com.example.DataProcessor.calculate(DataProcessor.java:68)at com.example.DataProcessor.run(DataProcessor.java:32)at java.lang.Thread.run(Thread.java:748)
分析:DataProcessor.calculate
方法第 68 行存在死循环(如while(true)
未加退出条件),导致线程持续占用 CPU。解决方案:修复死循环逻辑,添加合理的退出条件。
四、性能调优辅助工具
1. jvisualvm - 图形化综合分析
功能:JDK 自带的图形化工具,集成了 jps、jstat、jmap、jstack 的功能,支持实时监控、堆分析、线程分析、性能采样,适合开发环境快速排查。使用步骤:
- 命令行输入
jvisualvm
启动工具。 - 在左侧 “本地” 节点下找到目标进程(如 1234),双击连接。
- 关键功能:
- 监控:实时查看 CPU、内存、类加载、线程数量变化。
- 线程:查看线程状态,点击 “检测死锁” 可自动识别死锁。
- 抽样器:选择 “CPU 抽样”,运行一段时间后查看 “热点方法”,快速定位耗时最长的代码(如频繁调用的工具类方法)。
案例:调优接口响应慢通过 “CPU 抽样” 发现UserService.queryUser
方法耗时占比达 60%,进一步查看代码,发现该方法每次查询都新建数据库连接,未使用连接池。解决方案:集成数据库连接池(如 HikariCP),复用连接,接口响应时间从 500ms 降至 50ms。
2. Arthas - 线上诊断神器
功能:阿里开源的在线诊断工具,无需重启应用即可实现反编译、线程分析、性能采样、堆快照等功能,适合生产环境排查。常用命令案例:
- dashboard:实时查看进程概览,包括 CPU、内存、GC、线程数:
Arthas dashboard
ID NAME GROUP PRIORITY STATE %CPU TIME INTERRUPTED DAEMON
123 http-nio-8080-exec-1 main 5 RUNNABLE 30.0 0:12 false true
456 GC task thread#0 (G1) system -1 RUNNABLE 15.0 0:08 false true
- thread -b:快速定位阻塞线程的源头:
thread -b
Found one blocked thread:
"http-nio-8080-exec-5" Id=23 BLOCKED on com.example.OrderService@6b8e4260 owned by "http-nio-8080-exec-3" Id=21at com.example.OrderService.createOrder(OrderService.java:45)- blocked on com.example.OrderService@6b8e4260at com.example.OrderController.addOrder(OrderController.java:28)
- profiler start/stop:CPU 性能采样,生成火焰图(需安装 graphviz),直观展示方法调用耗时占比,快速定位性能瓶颈。