跳到主要内容

JDK命令行诊断工具

在Java应用的开发和运维过程中,性能问题、内存泄漏、线程死锁等故障时有发生。掌握JDK自带的命令行诊断工具,能够快速定位问题根源,是每个Java工程师必备的技能。

JVM诊断工具全景图

所有命令行工具都位于JDK安装目录的bin文件夹下,可直接在终端使用。

jps: Java进程状态查看

jps(Java Virtual Machine Process Status Tool)用于列出当前系统中所有运行的Java进程。

基础使用示例

# 查看所有Java进程
$ jps
8234 OrderService
9156 ProductService
9832 Jps
10456 PaymentService

# 显示完整类名
$ jps -l
8234 com.example.order.OrderServiceApplication
9156 com.example.product.ProductServiceApplication
9832 sun.tools.jps.Jps
10456 com.example.payment.PaymentServiceApplication

# 查看JVM启动参数
$ jps -v
8234 OrderService -Xms2g -Xmx2g -XX:+UseG1GC

# 查看传递给main方法的参数
$ jps -m
9156 ProductService --spring.profiles.active=prod --server.port=8081

# 组合使用
$ jps -lmv

jps参数说明

参数说明使用场景
无参显示PID和主类简名快速查看进程
-l显示完整类名或JAR路径区分同名应用
-m显示main方法参数查看启动配置
-v显示JVM参数查看内存配置
-q只显示PID脚本使用

jstat: 虚拟机统计监控

jstat(JVM Statistics Monitoring Tool)实时监控JVM运行状态,包括类加载、内存、GC等信息。

命令格式

jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
  • option: 统计选项(gc、gcutil、class等)
  • -t: 输出时间戳列
  • -h<lines>: 每隔多少行输出一次表头
  • vmid: 虚拟机进程ID
  • interval: 查询间隔(毫秒)
  • count: 查询次数

常用统计选项

实战示例: 监控GC情况

# 每隔2秒输出一次GC统计,共输出5次,每3行输出表头
$ jstat -gc -t -h3 8234 2000 5

Timestamp S0C S1C S0U S1U EC EU OC OU MC MU YGC YGCT FGC FGCT GCT
156.2 25600.0 25600.0 0.0 12800.5 204800.0 156234.3 512000.0 89432.1 51200.0 48932.5 45 0.342 2 0.158 0.500
158.2 25600.0 25600.0 8234.1 0.0 204800.0 98234.7 512000.0 92341.5 51200.0 49123.8 46 0.351 2 0.158 0.509
160.2 25600.0 25600.0 0.0 15678.2 204800.0 178945.2 512000.0 95678.3 51200.0 49345.2 47 0.359 2 0.158 0.517

# 输出GC百分比统计
$ jstat -gcutil 8234 1000 3
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 50.00 76.32 17.46 95.58 92.06 45 0.342 2 0.158 0.500
32.15 0.00 48.05 18.03 95.71 92.19 46 0.351 2 0.158 0.509
0.00 61.24 87.36 18.69 96.38 92.63 47 0.359 2 0.158 0.517

字段说明

字段说明单位
S0C/S1CSurvivor0/1区容量KB
S0U/S1USurvivor0/1区已使用KB
EC/EUEden区容量/已使用KB
OC/OU老年代容量/已使用KB
MC/MU元空间容量/已使用KB
YGC/YGCTYoung GC次数/总耗时次/秒
FGC/FGCTFull GC次数/总耗时次/秒
GCTGC总耗时

jinfo: 配置信息查询

jinfo(Configuration Info for Java)用于实时查看和动态调整JVM配置参数。

查看JVM参数

# 查看所有JVM参数和系统属性
$ jinfo 8234

# 查看指定参数值
$ jinfo -flag MaxHeapSize 8234
-XX:MaxHeapSize=2147483648

$ jinfo -flag UseG1GC 8234
-XX:+UseG1GC

# 查看所有可管理的参数
$ jinfo -flags 8234

动态修改参数(无需重启)

# 开启GC日志打印
$ jinfo -flag +PrintGC 8234

# 关闭GC日志打印
$ jinfo -flag -PrintGC 8234

# 开启显式GC禁用
$ jinfo -flag +DisableExplicitGC 8234

# 修改参数值
$ jinfo -flag HeapDumpPath=/tmp/dumps 8234

可动态修改的参数

参数说明
PrintGCGC日志打印
PrintGCDetailsGC详细日志
PrintGCTimeStampsGC时间戳
HeapDumpPath堆转储路径
HeapDumpOnOutOfMemoryErrorOOM时自动dump

jmap: 堆内存转储

jmap(Memory Map for Java)生成堆内存快照文件,用于离线分析内存使用情况。

生成堆转储文件

# 生成堆dump文件(二进制格式)
$ jmap -dump:format=b,file=/tmp/heap_dump.hprof 8234
Dumping heap to /tmp/heap_dump.hprof ...
Heap dump file created [1234567890 bytes in 12.345 secs]

# 仅转储存活对象(触发Full GC)
$ jmap -dump:live,format=b,file=/tmp/heap_live.hprof 8234

# 查看堆内存摘要信息
$ jmap -heap 8234

查看对象统计信息

# 统计各类对象的数量和占用空间
$ jmap -histo 8234 | head -20

num #instances #bytes class name
----------------------------------------------
1: 145678 23456789 [C
2: 56789 12345678 java.lang.String
3: 34567 8901234 java.util.HashMap$Node
4: 23456 7890123 com.example.order.Order
5: 12345 6789012 java.util.concurrent.ConcurrentHashMap$Node

# 只统计存活对象(会触发Full GC)
$ jmap -histo:live 8234

jmap注意事项

注意点说明
STW影响生成dump时会暂停应用
大堆耗时堆越大,dump时间越长
磁盘空间dump文件大小约等于堆使用量
生产谨慎避免频繁执行,避免-histo:live

jstack: 线程快照分析

jstack(Stack Trace for Java)生成线程堆栈快照,用于诊断死锁、线程阻塞等问题。

线程状态分类

基础使用

# 生成线程快照
$ jstack 8234 > thread_dump.txt

# 生成线程快照并显示锁信息
$ jstack -l 8234

# 强制生成快照(进程无响应时)
$ jstack -F 8234

死锁诊断实战

假设有如下银行转账死锁场景:

public class BankTransfer {
private static final Object accountA = new Object(); // 账户A锁
private static final Object accountB = new Object(); // 账户B锁

public static void main(String[] args) {
// 线程1: 从A转账到B
new Thread(() -> {
synchronized (accountA) {
System.out.println("Transaction-1 locked Account-A");
try { Thread.sleep(100); } catch (InterruptedException e) {}

System.out.println("Transaction-1 waiting for Account-B lock...");
synchronized (accountB) {
System.out.println("Transaction-1 completed");
}
}
}, "Transaction-1").start();

// 线程2: 从B转账到A
new Thread(() -> {
synchronized (accountB) {
System.out.println("Transaction-2 locked Account-B");
try { Thread.sleep(100); } catch (InterruptedException e) {}

System.out.println("Transaction-2 waiting for Account-A lock...");
synchronized (accountA) {
System.out.println("Transaction-2 completed");
}
}
}, "Transaction-2").start();
}
}

使用jstack检测死锁

$ jstack 8234

Found one Java-level deadlock:
=============================
"Transaction-2":
waiting to lock monitor 0x00007f8a1c004a00 (object 0x000000076ab12340, a java.lang.Object),
which is held by "Transaction-1"

"Transaction-1":
waiting to lock monitor 0x00007f8a1c004b50 (object 0x000000076ab12350, a java.lang.Object),
which is held by "Transaction-2"

Java stack information for the threads listed above:
===================================================
"Transaction-2":
at BankTransfer.lambda$main$1(BankTransfer.java:24)
- waiting to lock <0x000000076ab12340> (a java.lang.Object)
- locked <0x000000076ab12350> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:750)

"Transaction-1":
at BankTransfer.lambda$main$0(BankTransfer.java:13)
- waiting to lock <0x000000076ab12350> (a java.lang.Object)
- locked <0x000000076ab12340> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:750)

Found 1 deadlock.

线程状态关键字

状态关键字说明可能原因
RUNNABLE正在运行或等待CPU正常执行
BLOCKED等待获取锁锁竞争
WAITING无限等待wait()/join()
TIMED_WAITING限时等待sleep()/wait(timeout)
waiting on condition条件等待park/网络IO
waiting for monitor entry等待进入同步块锁竞争激烈

命令行工具使用技巧

组合使用流程

监控脚本示例

#!/bin/bash
# gc_monitor.sh - GC监控脚本

PID=$1
INTERVAL=${2:-5000}
COUNT=${3:-100}
LOG_FILE="gc_monitor_$(date +%Y%m%d_%H%M%S).log"

echo "开始监控进程 $PID 的GC情况..."
echo "输出文件: $LOG_FILE"

jstat -gcutil -t $PID $INTERVAL $COUNT | tee $LOG_FILE

使用建议

  1. 组合使用命令: 先用jps定位进程,再用jstat监控,最后用jmap/jstack诊断
  2. 避免频繁dump: 生成堆转储会暂停应用,生产环境需谨慎操作
  3. 保留历史快照: 定期保存线程快照和GC日志,便于问题回溯
  4. 自动化脚本: 编写监控脚本定时采集jstat数据,绘制趋势图

JDK命令行工具是Java开发者最基础也是最重要的诊断手段。熟练掌握这些工具的使用,能够在问题发生时快速定位和解决,是每个Java工程师的必备技能。