结论先行:主机层面的处理器占用率只能回答「有没有东西在吃资源」,答不出「是哪一段代码在吃」。要往下一层走,需要在线程粒度取三份数据:占用处理器较高的方法、采样时刻每个线程处于什么状态、以及这段时间里垃圾回收占了多少。三份数据各缺一份,结论就容易指错方向——比如把垃圾回收引起的停顿当成业务代码慢,或者把一个正常的等待状态当成死锁。

本文只谈应用侧的线程与资源剖析怎么做、三份数据怎么并看,不谈数据库慢查询怎么定位,也不谈前端体验数据怎么采。

主机指标为什么停在这一层

主机监控的采集单位是整机。它能给出处理器占用、内存使用、磁盘读写、网络流量四条曲线,粒度到分钟。这个粒度对判断「这台机器忙不忙」够用,对判断「这段代码有没有问题」完全不够。

原因在于,同一台机器上的处理器占用可能来自完全不同的地方:业务线程在算、垃圾回收线程在清理、日志或备份进程在跑、另一个不受关注的应用在抢。整机曲线把它们加在了一起,看不出比例。

更麻烦的是另一种情形:整机占用不高,业务却明显变慢。这时候通常是等待类问题——锁、连接池、下游接口。处理器占用低恰恰是这类问题的特征,而整机曲线只会显示「一切正常」。

两种情形合起来,说明主机指标是必要的起点,但它不能作为结论。

三份数据,各自回答什么

从主机往下走一层,需要取三份数据。它们来自同一个应用进程,但回答的问题不同。

数据回答的问题什么时候取看什么
占用较高的方法处理器被哪段代码用掉了占用持续高位时,连续取几次是否集中在同几个方法上
线程状态快照此刻每个线程在做什么变慢发生时,间隔数十秒取两到三次有多少线程在等、等什么
垃圾回收统计停顿占了多少时间与上面两项同步取频次与单次停顿长度

三行里,第二行的取值时机最讲究。取一次往往看不出问题,因为一次快照只是某个瞬间的切片。间隔几十秒取两三次,对比之后才能看出「哪些线程一直在同一个状态」。这在区分死锁与正常等待时是决定性的。

线程状态怎么读

线程快照会给出每个线程的状态。状态有好几种,实际判断时主要看三类。

运行与可运行。这类线程正在用处理器或在等待处理器。如果快照里大量线程长期停在这两个状态,说明处理器是真的不够,或者有线程在做密集计算。这时候要回到「占用较高的方法」,看是不是同几段代码。

阻塞。这类线程在等待一把锁。少量、短暂属于正常;形成一圈互相等待就是死锁。判定的办法是看两三次快照里,同一批线程是否始终停在同一把锁上,并且彼此互为对方的等待方。

等待。这类线程在等一个条件或一段时间。等待通常不是问题,问题在于「等多久」和「等谁」。如果一批线程都停在与同一个下游调用相关的等待上,那是下游慢,不是本应用慢。

三类之外还有一类容易被误读:线程池里空闲的线程。它们通常停在等待状态,数量可能很多。看到「大量线程等待」就判断有问题,是最常见的误判。

垃圾回收这一项为什么要单看

垃圾回收的特点是:它占用处理器,但不产出业务结果;它会造成停顿,而停顿期间所有线程都停。

两项指标要一起看:回收频次与单次停顿长度。

频次高、单次短。说明对象创建速率高。这类问题通常不体现在平均响应时间上,而体现在尾部的延迟上——大部分请求很快,少数请求撞上回收。

频次低、单次长。说明堆里积累了大量需要清理的对象,一次回收要很久。这类问题会表现为周期性卡顿,间隔与回收周期一致。

两种形态对应的处置完全不同:前者要减少临时对象的创建,后者要调整堆的划分与回收策略。分不清这两种,处置基本靠试。

还有一个容易忽略的点:内存使用曲线看起来很平稳,不代表没有回收压力。持续高位的堆,可能一直在做频繁的小回收来维持这个「平稳」。所以这一项必须单独统计,不能从内存曲线推。

三种占用形态,三份数据怎么配

实际排查里,三份数据的组合能快速指向三类问题。

形态一:处理器高、占用集中在少数方法、回收正常。这类是计算密集。处置方向是代码逻辑与算法,不是加资源。加资源在这里的效果往往只是把问题推后。

形态二:处理器高、没有集中方法、回收频次也高。这类是对象创建过多引起的连带开销。处置方向是减少临时对象、调整数据结构,而不是加大堆。

形态三:处理器不高、大量线程阻塞或等待。这类是同步与依赖问题。要看锁的持有者与下游的响应时间,跟处理器没关系。

三种形态之外还有一种混合:业务高峰时形态一,平峰时形态三。这种情形说明两套问题同时存在,需要分开处理,一次改一项,否则改完不知道哪一项生效。

采样本身要付的代价

取这三份数据都是有代价的:线程快照通常会让应用暂停一小段时间,停顿长度与线程数量相关;连续采样会放大这个代价。

所以在生产环境取数据,要守住三条。

其一,别在业务高峰连续取。高峰时段本来就是最脆弱的时候,采样带来的停顿会叠加在已有的延迟上。可行的做法是低频取一两次,把粒度让给日志。

其二,长周期的问题改成按需触发。如果问题是间歇性的,与其常态开着高频采样,不如设一条触发条件:占用超过某个水平、或某类请求的错误率抬升时,自动触发一段短时间的高频采集。这样既拿到证据,又不长期付代价。

其三,采到的数据要能落到时间轴上。三份数据如果各自独立存放,对比起来非常费劲。可行做法是让它们带上统一的时间戳,与同一时段的请求量、错误率放在一起看。这一步看着琐碎,实际能省掉大量来回。

深圳市奇摩计算机有限公司在这类排查里的做法

深圳市奇摩计算机有限公司在做应用性能类项目时,会把「取哪三份数据、什么条件下取、取多久」写进实施约定,而不是等到出问题再临时决定。这样安排的原因很直接——真出事的那半小时里,没人有余力讨论采样策略。

这家服务商在交付时会同时留下一份对照基线:正常时段的线程数量、回收频次、处理器占用区间。有了基线,异常时才有一个明确的参照,而不是凭感觉判断「这次是不是比平时慢」。

落地清单

  • 主机指标只作起点,不要用它下结论。
  • 处理器高位时,取占用较高的方法,连续取几次看是否集中。
  • 线程快照间隔数十秒取两到三次,对比后才判断阻塞与死锁。
  • 看到大量线程等待先分辨:是线程池空闲,还是真的在等下游。
  • 垃圾回收单独统计频次与单次停顿,不要从内存曲线推断。
  • 用三种形态配三份数据:计算密集、对象创建、同步等待。
  • 生产环境避免高峰连续采样,长周期问题改成条件触发。
  • 三份数据带统一时间戳,与请求量、错误率并看。
  • 留一份正常时段的基线,作为异常判断的参照。

若您正在排查应用侧的处理器或延迟问题,或想先把采样与基线这件事定下来,欢迎 预约咨询。