一、为什么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>

五、预防与优化建议

  1. 代码审查时特别注意循环和递归
  2. 压测环境模拟真实流量
  3. 重要服务添加CPU使用率监控
  4. 定期进行性能测试
  5. 建立性能基线指标

就像汽车需要定期保养,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%。