本文目录导读:

我来详细介绍Java性能瓶颈定位的实战排查方法和案例。
常见瓶颈类型与症状
CPU瓶颈
# 症状:CPU使用率持续>90%,响应时间变慢 top -Hp <pid> # 查看线程CPU使用 ps -mp <pid> -o THREAD,tid,time # 线程CPU时间
内存瓶颈
# 症状:频繁GC,OutOfMemoryError,响应变慢 jstat -gcutil <pid> 1000 10 # GC情况,每1秒查一次 jmap -heap <pid> # 堆内存使用
IO瓶颈
# 症状:磁盘读写慢,网络延迟高 iostat -x 1 # 磁盘IO netstat -an | grep TIME_WAIT | wc -l # 连接状态
实战排查案例
案例1:高CPU问题排查
现象:应用响应变慢,CPU使用率95%
排查步骤:
# 1. 找到高CPU的进程 top # 输出:PID=12345, CPU=95% # 2. 找到高CPU的线程 top -Hp 12345 # 输出:TID=23456, CPU=30% # 3. 将线程ID转换为16进制 printf "%x\n" 23456 # 输出:5ba0 # 4. 获取线程堆栈 jstack 12345 | grep -A 20 "5ba0" # 或者使用线程dump分析 jstack -l 12345 > thread_dump.txt
堆栈分析示例:
// 发现热点代码
"Thread-1" #12 daemon prio=5 os_prio=0 tid=0x00007f...
java.lang.Thread.State: RUNNABLE
at com.example.service.OrderService.processOrder(OrderService.java:45)
- 正在执行大量循环计算
案例2:内存泄漏排查
现象:内存持续增长,频繁Full GC
# 1. 查看GC情况 jstat -gcutil 12345 1000 10 # 输出:FGC=200, FGCT=150s (Full GC频繁) # 2. 生成堆转储 jmap -dump:live,format=b,file=heap.hprof 12345 # 3. 使用MAT分析堆转储 # 打开heap.hprof,查看:
MAT分析步骤:
// 查找怀疑泄漏的类 // 1. Histogram视图查看对象数量 // 2. Dominator Tree找出大对象 // 3. Path to GC Roots分析引用链 // 常见泄漏场景: // - HashMap无限增长 // - ThreadLocal未清除 // - 内部类持有外部类引用
案例3:线程死锁排查
现象:部分请求超时,系统无响应
# 1. 获取线程堆栈 jstack -l 12345 > jstack.txt # 2. 自动检测死锁 jstack 12345 | grep -A 20 "deadlock" # 或 jconsole -> 线程 -> 检测死锁
死锁示例:
"Thread-1" prio=5 tid=0x...
at com.example.service.AccountService.transfer(AccountService.java:25)
- waiting to lock <0x00000000d4f2b6c0> (a java.lang.String)
- locked <0x00000000d4f2b6d8> (a java.lang.String)
"Thread-2" prio=5 tid=0x...
at com.example.service.AccountService.transfer(AccountService.java:25)
- waiting to lock <0x00000000d4f2b6d8> (a java.lang.String)
- locked <0x00000000d4f2b6c0> (a java.lang.String)
系统级监控命令
CPU分析
# 使用perf进行采样分析 perf top -p <pid> perf record -g -p <pid> -- sleep 30 perf report # 使用火焰图 git clone https://github.com/brendangregg/FlameGraph perf script | ./stackcollapse-perf.pl > out.perf-folded ./flamegraph.pl out.perf-folded > cpu.svg
内存分析
# 查看对象占用 jmap -histo:live <pid> | head -20 # 查看大对象 jmap -histo:live <pid> | sort -n -k 3 | tail -20 # 实时查看GC日志 jstat -gccause <pid> 1000
IO分析
# 文件IO strace -e trace=write,read -p <pid> -c # 网络IO lsof -i -n -P | grep <pid> ss -tpn | grep <pid>
APM工具集成
使用Arthas(推荐)
# 安装
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
# 常用命令
# 查看方法调用耗时
trace com.example.service.OrderService processOrder
# 查看方法调用栈
stack com.example.service.OrderService processOrder
# 查看方法参数返回值
watch com.example.service.OrderService processOrder '{params,returnObj}'
使用VisualVM
# 启动VisualVM,连接到本地或远程JVM # 可以实时监控: # - CPU/内存使用 # - 线程状态 # - GC活动 # - 采样分析
实战案例:完整排查流程
场景:用户投诉订单提交慢,平均耗时从50ms上升到5s
步骤1:快速定位
# 1. 查看系统负载 top # CPU正常,内存使用率高 # 2. 查看GC情况 jstat -gcutil <pid> 1000 # Full GC频繁,每2秒一次 # 3. 生成堆转储 jmap -dump:live,format=b,file=heap.hprof <pid>
步骤2:分析堆转储
// 使用MAT分析发现: // 1. Order对象有100万个,但只应保留10万 // 2. 查看引用链 -> OrderCache持有大量引用 // 3. 缓存未设置过期策略
步骤3:定位问题代码
# 使用Arthas深入分析 trace com.example.cache.OrderCache getOrder # 发现缓存命中率低,每次都从数据库加载
步骤4:修复和验证
// 问题代码
public class OrderCache {
private Map<String, Order> cache = new HashMap<>();
public Order getOrder(String orderId) {
Order order = cache.get(orderId);
if (order == null) {
order = orderDao.getById(orderId);
cache.put(orderId, order); // 缓存无限增长
}
return order;
}
}
// 修复方案:使用Guava Cache或设置过期策略
private Cache<String, Order> cache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
// 或者使用LRU缓存
最佳实践总结
- 监控先行:部署APM工具,如Prometheus+Grafana
- 日志规范:统一日志格式,包含调用链ID
- 压力测试:上线前进行性能测试
- 渐进排查:从系统层→JVM层→代码层
- 保留现场:问题复现时立即保存dump和日志
瓶颈定位不是目的,解决瓶颈才是关键。 排查问题时要关注业务场景和影响范围,合理评估优化方案的收益和成本。