一个让人困惑的现象
用户反馈页面卡死,接口超时。你打开监控:CPU 30%,内存正常,Grafana 一切绿色。
从资源角度看,系统完全健康。但它就是不能用。
CPU 低,只能说明系统没在算,不代表系统没在等。
三种常见根因
1. 线程池 I/O 阻塞
Tomcat 的 worker 线程在等待 I/O 操作(数据库查询、外部 API 调用),新请求进来没有线程处理,只能排队。
线程状态:大量 WAITING / TIMED_WAITING,CPU 自然不高。
2. 连接池耗尽
数据库 CPU 40%,看起来正常。但连接池已满(50/50),应用拿不到连接,请求全部阻塞。
这个问题在只看 CPU 和 QPS 的看板里完全看不出来。
3. 慢接口拖垮吞吐量
一个接口从 50ms 变成 300ms,线程池开始积压,P95/P99 延迟飙升,整体吞吐量下降——但 CPU 依然很低。
诊断方法
查线程状态
jstack [PID] | grep -c "WAITING\|BLOCKED"
查线程池饱和度
// 检查活跃线程数 vs 最大线程数
executor.getActiveCount() vs executor.getMaximumPoolSize()
查 GC 情况
jstat -gcutil [PID] 1000 10
频繁 Young GC + Stop-The-World 暂停,会导致请求延迟抖动。
查堆对象
jmap -histo [PID] | head -20
找出占用内存最多的对象类型,判断是否有内存泄漏。
传统监控的盲区
| 监控指标 | 能发现的问题 | 发现不了的问题 |
|---|---|---|
| CPU 使用率 | 计算密集型问题 | I/O 等待、线程阻塞 |
| 内存使用率 | 内存不足 | 内存泄漏早期、GC 压力 |
| 数据库 QPS | 查询量异常 | 连接池耗尽、慢查询积压 |
| 接口成功率 | 明显错误 | 响应时间退化、超时积压 |
结论
成熟的运维团队,在系统「变慢」的早期阶段就能介入,而不是等到「崩溃」才响应。
这需要监控 JVM 内部状态,而不只是基础资源指标:线程池饱和度、连接池可用数、P95/P99 延迟、GC 频率——这些才是真正反映系统健康的指标。