一、为什么Java应用CPU会飙高?
咱们程序员最怕的就是半夜被报警电话吵醒,打开监控一看CPU直接飙到99%。这种情况就像你家的水龙头突然爆裂,水压直接冲上天花板。Java应用CPU飙高通常有几种典型症状:服务响应变慢、监控曲线直线上升、甚至整个容器直接OOM。
举个例子,我们有个电商系统在促销时出现过这样的场景:
// 技术栈:SpringBoot + MyBatis
@RestController
public class OrderController {
// 这个查询在订单量暴增时变成了性能杀手
@GetMapping("/orders")
public List<Order> getOrders(@RequestParam String userId) {
// 问题点:没有分页控制的全表扫描
return orderMapper.selectByUserId(userId);
}
}
这种情况就像你去图书馆借书,管理员却把整个图书馆的书架都搬给你,不累瘫才怪。
二、诊断CPU问题的十八般武艺
2.1 先用top锁定目标
就像医生看病要先量体温,我们先用top命令看看哪个进程在"发烧":
top -c
# 按1显示所有CPU核心
# 按P按CPU排序
# 记录下PID和CPU占用率
2.2 jstack抓取线程快照
拿到PID后,我们需要看看Java进程内部哪些线程在疯狂工作:
jstack -l <pid> > thread_dump.log
这个命令就像给JVM拍X光片,能看清所有线程的堆栈信息。
2.3 用jstat看GC情况
有时候CPU高是因为GC在拼命工作:
jstat -gcutil <pid> 1000 5
# 每秒采样一次,共5次
# 关注FGC/FGCT列的老年代GC情况
2.4 arthas实时诊断
阿里开源的arthas就像Java应用的听诊器:
# 启动arthas
./arthas-boot.jar
# 选择目标进程
# 监控最忙的线程
thread -n 3
# 方法执行耗时统计
trace com.example.Service *
# 实时监控方法调用
monitor -c 5 com.example.Service getOrders
三、常见问题场景分析
3.1 死循环陷阱
来看个典型的死循环案例:
// 技术栈:纯Java
public class LoopProblem {
public static void main(String[] args) {
while (true) {
// 忘记条件的死循环
processData();
}
}
private static void processData() {
// 假装这里有些处理逻辑
}
}
这种代码就像仓鼠在跑轮,拼命跑却永远到不了终点。
3.2 锁竞争激烈
高并发下的锁竞争就像早高峰的地铁闸机:
// 技术栈:Java并发
public class LockContention {
private final Object lock = new Object();
public void doWork() {
synchronized(lock) {
// 耗时操作放在同步块内
heavyProcessing();
}
}
}
更好的做法是把耗时操作移到锁外面,就像地铁站让大家先准备好乘车码再排队过闸机。
3.3 不合理的算法
算法复杂度爆炸就像用勺子舀干游泳池:
// 技术栈:Java集合
public class BadAlgorithm {
public void processUsers(List<User> users) {
// 双重循环O(n²)复杂度
for (User u1 : users) {
for (User u2 : users) {
compare(u1, u2);
}
}
}
}
四、进阶诊断技巧
4.1 火焰图分析
使用async-profiler生成火焰图:
./profiler.sh -d 30 -f flamegraph.html <pid>
火焰图就像CPU使用的热力图,哪里"发烫"一目了然。
4.2 JFR飞行记录
Java Flight Recorder是Oracle提供的黑匣子:
# 开启记录
jcmd <pid> JFR.start duration=60s filename=recording.jfr
# 分析记录
jfr print --events cpu --stack-depth 64 recording.jfr
4.3 内存与CPU的关联分析
有时候CPU高是因为内存问题:
# 查看内存分配压力
jmap -histo:live <pid>
五、预防与优化建议
- 代码审查时特别注意循环和递归
- 压测环境模拟真实流量
- 重要服务添加CPU使用率监控
- 定期进行性能测试
- 建立性能基线指标
就像汽车需要定期保养,Java应用也需要持续的性能优化。记住:预防永远比救火来得轻松。
六、实战案例分享
最近处理的一个真实案例:某金融系统在交易日开盘时CPU飙升。通过arthas发现是风控计算模块的问题:
// 问题代码示例
public class RiskCalculator {
public BigDecimal calculate(List<Position> positions) {
return positions.stream()
.map(p -> {
// 每次都在重复创建相同的计算模型
RiskModel model = new ComplexRiskModel();
return model.calculate(p);
})
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
优化方案:改用预计算和缓存模式,CPU使用率直接下降70%。
评论