
生产环境 order-service 实例触发 CPU 阈值告警。监控数据呈现以下特征:
%us) 攀升至 82%,System-space CPU (%sy) 为 12%,iowait 为 0%。运行队列长度(Load Average)达到 6.7,显著高于机器核数(4C)。环境基线:CentOS 7.9 (Kernel 3.10.0),OpenJDK 8 (G1 GC),Kubernetes 宿主机工作负载。
使用 top 查看整体消耗,排除 I/O 或中断异常:
top - 15:23:01 up 120 days, load average: 6.72, 5.34, 4.12
%Cpu(s): 82.3 us, 12.1 sy, 0.0 ni, 4.8 id, 0.0 wa, 0.0 hi, 0.8 si, 0.0 st
PID USER %CPU COMMAND
12345 root 185.3 java判定逻辑:%us 远高于 %sy 且 %wa 为 0,可立即排除磁盘 I/O 瓶颈和内核态驱动问题,锁定故障源为 Java 应用用户态逻辑。
使用 mpstat -P ALL 1 观测单核是否被打满。若单核 %usr 显著高于其他核,则说明存在单线程计算密集型任务或锁迁移(Lock Hopping);若所有核均衡飙高,则属于全局性资源竞争或 GC 扰动。本案例中四核均衡,进入下一步。
使用 pidstat 分解进程内各线程的 CPU 消耗,这是从进程级降维到线程级的关键步骤:
pidstat -t -p 12345 1 5输出重点:
TID | %usr | %system | Command |
|---|---|---|---|
12347 | 85.0 | 3.0 | java |
12348 | 80.0 | 4.0 | java |
12346 | 2.0 | 0.0 | java |
结论:TID 12347 与 12348 贡献了绝大部分 CPU,需拆解其线程栈。
在不重启进程的前提下,使用 perf 抓取 CPU 正在执行的硬件指令符号,用于区分 CPU 消耗在 业务计算 还是 JVM 底层运行时:
perf top -p 12345输出热点符号:
G1CollectedHeap::allocate_new_tlab (45.23%)__intel_ssse3_rep_memcpy (20.11%)OrderPriceCalculator.calculateDiscount (12.56%)_raw_spin_unlock_irqrestore (8.21%)原理解读:
G1CollectedHeap::allocate_new_tlab 占用极高,说明 JVM 分配新对象的频率远超预期,TLAB(Thread Local Allocation Buffer)被频繁消耗并请求 Eden 区分配。这并非 GC 线程本身消耗,而是业务线程在申请内存时的 JVM 内部开销。
针对高 TID 使用 strace 统计系统调用频次:
strace -f -p 12347 -c -e trace=futex,clock_gettime统计显示 futex 系统调用频次达 2000 次/秒,说明 JVM 内部存在严重的用户态锁竞争(synchronized 或 ReentrantLock),导致线程频繁触发内核态切换(sys 时间占比上升)。
将 TID 转为十六进制(printf '%x\n' 12347 得 0x303b),抓取线程栈:
jstack -l 12345 | grep -A 30 'nid=0x303b'栈顶指向业务代码:
at com.order.OrderPriceCalculator.calculateDiscount(OrderPriceCalculator.java:45)
- waiting to lock <0x00000000d5a6b3e0> (a java.lang.Object)
at java.math.BigDecimal.divide(BigDecimal.java:1703)双重根因锁定:
calculateDiscount 使用了方法级 synchronized,高并发下形成阻塞队列。BigDecimal 除法运算在循环中不断创建不可变对象,加剧 TLAB 压力,诱使 perf 观测到的 allocate_new_tlab 飙升。元(浮点/小数)转为 分(long 整型),消除 BigDecimal 的临时对象分配。单次请求对象分配数量从 2.4 万个降至 0.2 千个。synchronized,利用 ConcurrentHashMap 配合 AtomicLong 做细粒度缓存控制,减少 futex 上下文切换。修改启动参数,降低内存分配时的 JNI 边界开销:
-XX:+UseG1GC
-XX:TLABSize=8m # 默认 1m,增大以减少 TLAB 重新申请次数
-XX:NewRatio=2 # 扩大年轻代比例,降低 YGC 频率
-XX:+UseStringDeduplication # 去重重复字符串,降低内存复制开销
-XX:MaxGCPauseMillis=50 # 收紧 G1 预期停顿,抑制 Full GC 风险调整上线后,通过 jstat -gcutil 和 top 验证:
指标 | 优化前 | 优化后 |
|---|---|---|
用户态 CPU (%us) | 82% | 24% |
YGC 频率 | 15 次/分钟 | 2 次/分钟 |
TLAB 分配耗时 (perf 占比) | 45% | 3.2% |
P99 接口延迟 | 1200 ms | 65 ms |
上下文切换 (cs) | 3500 次/s | 800 次/s |
若 Pod 设置了 cpu limits,宿主机会强制启用 CFS Bandwidth Control。当 CPU 使用量触及 quota 时,内核会向进程发送 throttled 信号。排查此类问题需观测:
cat /sys/fs/cgroup/cpu/kubepods/pod-xxx/cpu.stat若 nr_throttled 持续增长,说明 CPU 资源已不足以支撑业务峰值,需在提高 Limit 与优化代码间权衡。本案例在优化代码后,CPU 用量低于 Limit,Throttle 自动消失。
阶段 | 命令 | 目的 |
|---|---|---|
全局负载 | top -c,vmstat 1 | 区分 us/sy/wa/steal |
内核/中断 | mpstat -P ALL 1,cat /proc/interrupts | 判断软中断是否过高 |
线程级拆分 | pidstat -t -p [PID] 1 | 找出具体消耗 CPU 的 TID |
内核指令拆解 | perf top -p [PID] -g | 看 JVM 底层是计算型还是分配型 |
系统调用频率 | strace -c -p [TID] | 看 futex/read/write 是否异常 |
堆内分配速率 | jstat -gcutil [PID] 1s | 观测 Eden/Survivor 晋升速率 |
本次案例揭示了高 CPU 使用率的双重幻觉:top 看到的用户态高占用,实则由 JVM 内存分配子系统的内部开销(TLAB 填充)与底层 futex 锁切换共同构成。单纯的代码业务逻辑仅占 12.56% 的 CPU 指令周期。因此,Linux SRE 在处理 Java 类应用时,必须建立从top(系统层) -> perf(指令层) -> jstack(应用层)的三级分析模型,才能精准识别瓶颈根因。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。