1. 排查生产环境变慢的第一性原理生产环境“变慢”最怕的不是问题本身而是面对一堆监控数据不知道从哪里下手。平均响应时间从 80ms 跳到 400msCPU 使用率却只有 30%内存也没满GC 也正常。这种“三高都不高”的假象最迷惑人因为你根本无法从资源水位判断瓶颈到底卡在哪一环。我的习惯是先把问题拆成两个维度时间花在哪以及CPU 在忙什么。这两个问题正好对应 strace 和 perf 的核心能力。strace 看系统调用耗时能告诉你进程是不是在等什么资源比如网络读、磁盘写、锁竞争perf 看 CPU 在执行的代码路径能告诉你 CPU 周期到底消耗在哪个函数、哪条调用链上。两者一个偏“等待”一个偏“执行”配合起来刚好覆盖了绝大多数性能问题的全貌。很多人一上来就用 strace 全量抓系统调用或者 perf 直接 record 整个进程结果日志爆炸、文件巨大最后既看不出问题又白白让被调试的进程更卡。磨刀不误砍柴工磨的就是先想清楚这一步当前现象是 CPU 忙还是进程等如果是 CPU 忙优先 perf如果是进程等优先 strace。但更常见的情况是两者交替使用比如先用 perf 看热点函数发现一个函数调用频繁但 CPU 时间并不高再用 strace 挂上去看是不是每次都在做无意义的系统调用。我在定位线上问题时很少一开始就跑到服务器上敲命令。我会先花几分钟把监控面板过一遍确认几个关键指标的趋势QPS、平均延迟、P99、CPU 使用率、磁盘 IO、网络重传、内核软中断。这些指标的组合能快速排除很多可能性比如磁盘满了导致 io 等待飙升或者网络重传导致请求迟迟无法返回这些问题根本不需要 perf 和 strace直接看监控就能定位。如果这些常规指标都正常才轮到我们的工具上场。什么时候用 perf什么时候用 strace什么时候两个一起用这取决于你观察到的“慢”发生在哪一层。如果 QPS 没有变化但延迟翻倍说明单个请求的处理时间变长了这大概率是 CPU 密集型问题perf 能直接告诉你热点如果 QPS 大量下降但延迟翻倍说明服务可能已经扛不住并发或者请求在 wait 状态大量堆积strace 能看到阻塞点。这套“先定性、再定量、后定位”的思路是我这几年排障过程中最核心的方法论。不过说再多不如做一遍下面我会把自己最常用的一套组合流程完整拆开讲包括命令怎么敲、输出怎么看、坑在哪里希望能给你一条可以直接抄作业的路径。2. perf 实战从采样到看懂热点函数的完整链路2.1 为什么 perf 比看监控更能说明问题监控能告诉你系统整体有多忙但很难告诉你“忙在哪个函数”。perf 的原理是基于硬件性能计数器进行采样简单说就是每隔一段极短的时间中断一次 CPU记录当前正在执行的指令地址和进程上下文最终形成一个统计学意义上的 CPU 时间分布。这个思路非常像“民意调查”——不统计每个公民的完整生活轨迹而是随机抽取足够多样本就能近似还原整体分布。perf 最大的优势在于开销低因为它依赖 CPU 硬件提供的 PMUPerformance Monitoring Unit采样频率默认 1000Hz 左右对生产环境的影响通常小于 3%。有一次我们线上 8 核的高负载服务QPS 大约 2 万我用 perf record 挂了 30 秒业务无明显感知采样文件也只有几百 KB处理起来很轻量。这是 strace 做不到的因为 strace 要拦截每一次系统调用开销可能放大好几个数量级特别是 syscall 频繁的场景甚至会直接让服务变成不可用状态。perf 的第二个优势是能看到整个调用链也就是不仅告诉你 CPU 花在哪个函数还能告诉你这个函数是被谁调进来的形成一条完成的调用栈。对定位跨模块问题特别有效尤其是用了第三方库的情况你能直接看到热点是来自自己的业务代码还是打入某个 SDK 的内部实现。调用链还可以生成火焰图视觉上一目了然。perf 的第三个优势是能采样的维度多不只是 CPU 周期还能观测分支预测失败、缓存 miss、锁竞争、内存带宽等硬件事件。虽然大多数场景我们用不上这么细但某些疑难杂症确实需要从硬件层找原因比如伪共享false sharing导致的性能骤降CPU 使用率高但看起来指令数并不多这时 perf stat 里 cache-miss 和总线争抢事件就能派上用场。2.2 perf record 采集的正确姿势以及采样时长的选择先上一个最常用的安全操作模板这也是我在线上第一轮排查时的标配命令# 建议用 root 或配置了 perf_event_paranoid 的用户执行 perf record -F 999 -g -p PID -- sleep 30 perf script out.perf-F 999表示采样频率 999Hz接近每秒千次这个频率在多数生产环境上是安全的。-g开启调用链记录后面分析火焰图时必选。-p指定进程号-- sleep 30让 perf 只追踪 30 秒就自动结束避免长时间挂载影响业务。采样结束后 out.perf 就是原始采样文本里面每一行记录了一次采样的调用栈之后可以喂给火焰图脚本或直接分析。我见过有人直接不指定-F用默认采样频率结果生成的报告文件巨大无比处理半天还看不出头绪。另一个常见的错误是采样时间过短比如只采 5 秒如果业务请求的延迟分布方差大很容易被局部抖动带偏结论。我个人经验至少采样 30 秒覆盖一个完整的业务周期比如在电商场景至少覆盖一次库存扣减支付回调的完整流程如果业务有明显的峰谷那就采样 2 分钟把高峰包进来。采样完别急着分析先用perf report快速看一眼方向perf report -i perf.data --stdio --sort comm,dso,symbol这个命令按进程、动态库、函数汇总输出每个符号的 CPU 占用百分比。第一次看输出时重点扫一眼 Top 20 有没有眼熟的函数——比如自己的业务代码、Redis 客户端、JVM 的 GC 线程、Nginx 的 worker 处理函数等。这里的“眼熟”指的是你能和业务逻辑对应上一般情况下热点出现在自己的代码里最好办出现在第三方库里就需要进一步看调用链判断是谁调进去的。我自己的习惯是perf report 只看一个大概方向真正要把瓶颈看清楚还得上火焰图。2.3 火焰图生成与关键指标解读火焰图是可视化调用栈采样的利器网上有开源脚本可以直接用我基于 Brendan Gregg 的 FlameGraph 项目封装了一个自己的脚本基本三步走# 第一步把 perf 采样的原始输出折叠成调用栈计数 ./stackcollapse-perf.pl out.perf out.folded # 第二步生成 SVG 火焰图 ./flamegraph.pl out.folded flame.svg # 第三步直接在浏览器打开 flame.svg火焰图的 X 轴是采样占比Y 轴是调用深度。看火焰图最重要的一点先看 X 轴上“平顶”的宽度也就是某一个函数占据的总宽度越大说明 CPU 在那个函数上耗时越多。一个合格的火焰图应该能让你在 10 秒内说出“CPU 主要花在 XX 函数上由 XX 调用链触发”而不是只看到一堆黄色的函数名。举一个实际例子我之前排查过一个订单服务变慢的问题。火焰图打开后发现有一个很宽的平顶落在了libc-2.17.so的__memcpy_avx_unaligned函数上超过 40% 的 CPU 时间都在里面。顺着调用链往下看是 JSON 序列化库在反复拷贝大字符串。看一下代码发现每次 RPC 响应都会把整个订单对象序列化但大部分字段下游根本用不到典型的过度序列化。后来改成只返回必要字段P99 延迟立刻下降了一半。看火焰图容易出错的点在于CPU 时间占比高不代表这个函数是问题源头。比如__memcpy_avx_unaligned很高可能是上游分配大内存导致的也可能是数据结构设计不合理导致频繁大块拷贝。这时候要结合调用链上的中间层去看确定是先有鸡还是先有蛋。我的习惯是顺着最宽的调用链逐层回溯直到找到业务代码入口或者某个明确的性能反模式比如内存分配密集、锁竞争激烈、序列化冗余。perf 还有两个很实用的变体一个是perf top实时刷新热点函数适合快速验证“改完代码再压测热点有没有消失”另一个是perf stat统计周期内的总体事件计数比如任务时钟、上下文切换次数、CPU 迁移次数、cache miss 率适合对比优化前后的系统指标。这两个命令不要和perf record混用perf top不落盘只实时看perf record是为了事后分析各有各的用途。3. strace 实战系统调用耗时并不是全部答案3.1 strace 真正擅长解决的场景strace 的原理是基于 ptrace 系统调用跟踪进程每一次进入内核的系统调用并记录返回值。代价很大所以 strace 不属于“先手工具”更适合在 perf 已经锁定某个方向后做定点验证。strace 最拿手的是定位“进程明明活着但请求就是不返回”的诡异问题。比如你通过监控发现一个服务的线程数在持续上涨但 CPU 使用率很低进程像“卡住”了一样。这时候用 perf 采样的效果通常不好因为 CPU 大部分时间在 idle热点函数根本采不到。而 strace 能告诉你每个线程到底阻塞在哪个系统调用上是read等待 socket 数据是futex等待锁释放还是nanosleep在主动打盹。还有一种典型场景服务偶发超时日志里却没有任何异常 stack。用 strace 挂载一段时间如果发现connect或者read上有超时等待且对端的 IP 和端口反复出现同一个值那大概率是某个下游依赖服务响应慢或者连接池打满。这类问题靠业务日志很难看到全貌因为业务框架往往把超时异常吞掉了只在上层返回一个 generic error。我还有一个必用的场景就是验证代码里的“重试逻辑”是否在疯跑。比如某个服务调用下游失败后代码在一个循环里疯狂重试每次重试都没有退避于是系统调用connect在极短时间内被触发几百次。用 strace 数一下connect的调用次数比看任何监控指标都快因为监控指标是秒级聚合strace 是逐次记录一秒发生几百次 connect 在监控图上可能只是一个毫秒级的小毛刺。3.2 strace 的常用参数组合与输出解读跟踪单个进程只显示系统调用名和返回值不显示参数strace -f -p PID -e tracenetwork,read,write,futex -t -o /tmp/strace.log各参数的含义-f跟随子线程和子进程对多线程服务是必须项否则你只能看到主线程的调用。-p PID指定需要跟踪的进程号。-e tracenetwork,read,write,futex过滤直跟踪这几类系统调用。这一步非常关键我强烈建议根据当前怀疑点缩小范围否则输出会像洪水一样刷屏反而淹没了真正的问题。-t显示每个系统调用的时间戳精确到秒。如果需要更精细的耗时可以用-tt显示微秒级时间戳配合两次相邻调用的时间差就能算出某一次调用的阻塞耗时。-o输出到文件而不是终端避免和业务日志互相干扰。strace 输出的核心三列系统调用名、参数列表、返回值。读懂返回值是排障的关键。read返回-1 EAGAIN表示当前非阻塞 socket 没有数据这本身不算异常但如果你看到read返回-1 ETIMEDOUT说明 socket 等待超时这就是网络问题的直接证据。connect返回-1 ECONNREFUSED说明对端端口没监听futex返回-1 EAGAIN通常代表锁竞争激烈获取锁失败后线程要进入睡眠等待。从分布上看生产环境服务最常见的阻塞系统调用就三类read、write、futex。read阻塞在读 socket 或读文件write阻塞在写 socket 缓冲区满或磁盘 IOfutex阻塞在用户态锁竞争。看到read阻塞且持续时间长下一步要确认对端是谁是 MySQL、Redis 还是下游 RPC 服务看到futex频繁且等待时间长则要把矛头指向锁设计本身比如是不是用了不加区分的全局锁。strace 的缺点也必须说清楚它会显著拖慢目标进程有时能达到 210 倍的速度下降。因此生产环境挂载 strace 必须快捷、短时、精准。我通常会分两次做第一次只跟踪 3~5 秒过滤出系统调用类型分布第二次再针对某一种调用跟踪 10 秒拿到时间戳细节。针对常驻进程先用-ccount模式统计系统调用次数分布再用-e trace聚焦也能有效降低干扰。3.3 一个易被忽略的坑容器写 Pid Namespace 导致 trace 失败现在很多服务跑在 Docker/K8s 里直接strace -p container_pid是有可能失败的因为容器内看到的 PID 和宿主机看到的 PID 并不一致。比如容器内进程 PID 是 42在宿主机上它可能是 18432。如果你在宿主机上执行strace -p 42很可能跟踪到了另一个不相干的进程更常见的是直接报错Operation not permitted。我踩过一次这样的坑当时想跟踪容器内 Java 进程的 GC 线程行为在宿主机用 pod 里的 PID 执行 strace结果持续报错我还以为是权限问题折腾了半天才意识到是 PID namespace 的问题。解决办法很简单在宿主机上先用docker inspect container_id | grep Pid拿到真实的宿主 PID或者直接进入容器再执行strace -p container_pid。# 获取容器对应宿主进程 PID docker inspect --format {{.State.Pid}} container_id # 也可以直接进容器内操作 kubectl exec -it pod_name -n namespace -- bash这个细节看起来小遇到一次能浪费半小时写出来提醒各位排查容器环境问题时先确认 PID 域再动手。3.4 学会用 strace 的“慢调用”统计功能快速圈定范围sleep不是系统调用-c统计的也只是系统调用次数但它能帮你快速看分布。不过比-c更有实操价值的是把时间维度加进来寻找慢系统调用。思路是用-T参数它会打印每次系统调用在内核态消耗的时间然后配合-o输出到文件再写一段小脚本找出耗时超过阈值的调用。strace -f -T -p PID -e traceall -o /tmp/strace_slow.log # 跟踪 5 秒后 kill 掉 strace sleep 5 pkill -f strace -f -T # 找出耗时超过 100ms 的系统调用 awk {print $2, $3, $(NF-1), $NF} /tmp/strace_slow.log | sort -k3 -rn | head -20这里的思路很简单正常系统调用耗时通常小于 1ms如果出现大量几十毫秒甚至秒级耗时说明存在明显的阻塞操作。根据阻塞系统调用的类型基本能锁定是哪一类资源出了问题。但要注意-T里统计的时间是内核执行时间不包含用户态代码的执行时间。如果热点在用户态计算上-T可能看不出什么这时还是回到 perf 比较靠谱。4. 一套真实案例从perf火焰图到strace验证再到代码修复4.1 现场情况与第一轮 perf 采样有一次线上促销活动结束后订单服务持续变慢平均响应时间从 100ms 涨到 500ms且没有自动恢复的迹象。我先看了监控CPU 并不高整体使用率 25% 左右内存充足网络和磁盘都没有明显打满。唯一的异常是线程数在不断上涨从正常的 200 涨到了 800 多。这种组合最典型的特征就是请求在等待某个东西而等待没有超时上限。CPU 不忙是因为大量线程挂起线程数上涨是因为挂起的请求不断新增形成堆积。这种情况下 perf 的 CPU 热点可能不够明显但可以用 perf 确认一下 CPU 到底花在哪里万一是 Code Cache 或锁导致的自旋那就直接对症下药。我按常规先打了 30 秒 perf recordperf record -F 999 -g -p PID -- sleep 30 perf script out.perf火焰图出来后确实没有单函数占比特别高的情况但看到一个有意思的细节——__lock_acquire相关的符号出现频率不低说明锁竞争是存在的。但 CPU 时间占比并不夸张说明锁竞争并不是 CPU 消耗的主力而更像是“等待锁释放”的挂起型竞争这个 CPU 看不出要 strace 出马。4.2 strace 发现 futex 疯狂等待接下来我上 strace先只统计系统调用分布strace -f -p PID -c -e tracefutex,read,write -o /tmp/strace_stat.txt sleep 5 pkill -f strace -f -p输出里futex调用次数多到离谱5 秒内超过 10 万次。这意味着每毫秒大概有 20 次锁相关的系统调用显然不正常。继续用-T模式记录一次筛选耗时strace -f -T -p PID -e tracefutex -t -o /tmp/strace_futex.txt sleep 5 pkill -f strace -f -T grep -E futex /tmp/strace_futex.txt | tail -100输出摘要如下12:03:04.123456 futex(0x7f8a2c0039d0, FUTEX_WAIT, 42, NULL) 0 0.002132 12:03:04.124567 futex(0x7f8a2c0039d0, FUTEX_WAIT, 42, NULL) 0 0.001849 12:03:04.125678 futex(0x7f8a2c0039d0, FUTEX_WAIT, 42, NULL) 0 0.003212每次都等待 2ms 左右成功唤醒但次数极其密集。这像极了两个线程在抢一把锁一个线程释放锁后另一个线程被唤醒但很快又释放然后再次竞争。锁的粒度太小、抢锁循环太频繁导致系统调用持续不断。这个痕迹至少说明锁已经不是“偶发竞争”而是“高频串行化”。到这一步问题方向已经很明确代码里某个全局锁在多线程下产生了严重竞争需要找到那把锁。4.3 结合代码定位锁粒度问题拿着 strace 给出的锁地址0x7f8a2c0039d0直接在代码里搜内存地址对应的对象显然不可行这时候需要一个取巧的做法就是围绕“高频且在热点路径上加锁”的嫌疑点去检查代码。火焰图里出现过的锁相关符号上下层往往会留下线索。我定位下来问题出在对一个全局缓存Map的读写上。代码用的是最简单的synchronized(getCache())方式而且这个缓存是热点路径每次请求都要读好几次。多线程并发读多写少的时候synchronized 会让所有读线程串行化即使读操作本身很快在高并发下也会瞬间形成大量 futex 等待线程数就这么堆起来了。这个案例说明 perf 和 strace 的关系perf 告诉你锁竞争存在strace 告诉你锁竞争已经严重到阻塞和唤醒风暴最终还是要靠代码审阅来定位到具体的那把锁。三个环节缺一不可。4.4 改造方案与前后对比在这个场景里读多写少天然适合ReadWriteLock或者ConcurrentHashMap。我当时的处理分两步先用ReadWriteLock分离读写锁读线程不再阻塞在synchronized上然后顺手优化了锁内逻辑把不必要的对象创建挪到了锁外。更彻底的做法是用ConcurrentHashMap的原子方法比如computeIfAbsent替代整表锁但这个方案需要评估弱一致性能不能接受。改造上线后我马上做了一次 A/B 对比锁等待次数从每 5 秒 10 万次降到 200 次以内。线程数从 800 多回落到正常水位 250 左右。P99 延迟从 500ms 降到 120ms。平均响应时间从 300ms 降到 90ms基本恢复到了促销前的水平。这组对比同时给了两个信息性能恢复到了正常而且锁确实是根因不是背锅。这种“修改-验证-对比”的闭环正是线上排障最让人踏实的部分。监控图上的恢复曲线很快就出来了领导看一眼就心里有底。4.5 问题速查表常见现象、工具与切入点我把排障过程中常见的现象、第一反应工具和核心关注点整理成了一个速查表建议收藏到笔记里遇到类似问题直接对照现象第一反应工具核心关注点CPU 使用率高单函数热点明显perf record 火焰图热点函数的调用链、是否过度序列化、是否内存拷贝大对象CPU 不高但线程数持续上涨strace -c -Tfutex/read/write 的等待时长与频率锁竞争或下游慢调用请求偶发超时无业务异常堆栈strace -e tracenetworkconnect/read 的超时、对端地址是否反复出现同一 IPGC 频繁且停顿明显perf record jstatGC 线程热点、内存分配路径、大对象是否过多压测时性能骤降CPU 100% 但 QPS 不涨perf stat cache-miss是否伪共享、是否 cache line 频繁争抢锁竞争严重但不拉 CPUstrace futex jstack锁地址、锁代码段、能否拆分锁或改无锁结构每条线索都有“下一步动作”不会让你停在“哦原来 CPU 在 XXX 函数”这种毫无行动力的结论上。5. 工具选型解析为什么是perfstrace而不是其他组合5.1 同类型工具对比与适用边界有一类观点认为用 APM 工具或者链路追踪就能定位性能瓶颈。没错分布式链路追踪能告诉你每个 Span 的耗时分布比如发现 Redis 调用耗时 200ms那你确实知道慢在了 Redis 这个下游。但如果你发现某个 Span 内部耗时高而 Span 里只是本地计算和函数调用那 APM 工具就看不到函数级别的细节了这就要 perf 这种采样工具补位。还有一类工具是 Java 自带的jstack。jstack能打出线程栈告诉你每个线程在哪个方法上等待这对定位锁竞争、死锁极其有效。但 jstack 只能看瞬间快照没办法告诉你 CPU 时间分布。如果你的问题表现在 CPU 热点上jstack 很难给出定量的结论。perf 能看 CPU 热点也能看调用链比 jstack 的范围更大。GDB 也是旁路工具之一。GDB 适合按暂停状态分析比如看某个线程当前停在哪个函数或者 dump 线程堆栈。但 GDB 对生产环境的影响更大因为 attach 到进程会暂停进程运行对于要求高可用的服务基本不可接受。strace 的开销是渐进的不会一下把进程暂停住所以线上可操作性比 GDB 好很多。5.2 组合使用的时机与顺序我的经验是不要设计一个固定的“公式”而是要学会根据现象判断从哪一侧切入。下面是我最常用的几条判断路径现象是 CPU 飙高直接 perf record 火焰图。火焰图找到热点函数后再看调用链是否对应异常代码。如果热点函数在锁上再用 strace 验证锁竞争程度。现象是线程数飙升、CPU 不高先 strace -c 统计系统调用分布快速判断是 futex 还是 read/write。如果是 futex结合 jstack 或 pstack 看线程栈如果是 read/write结合 lsof 或 ss 确认对端资源。现象是偶发超时且无规律先用 strace 挂载观察一段时间记录超时点的系统调用和耗时如果 strace 看不到规律再用perf probe或者perf trace定向跟踪业务函数的执行时长。另外有一个很多人容易忽视的操作顺序问题strace 和 perf 不能同时对同一个进程长时间运行。它们都会干扰进程性能叠加起来可能造成不可预测的影响。稳妥做法是先 perf 后 strace或者先 strace 后 perf但不要让它们长时间并行。5.3 内核版本与权限的前置检查perf 和 strace 都依赖内核特性但绝大多数现代 Linux 发行版开箱即用。需要注意权限问题特别是容器环境。Linux 默认的perf_event_paranoid值可能是 2这个配置禁止非 root 用户使用性能事件。如果你的服务用户不是 root并且没有权限改系统配置perf 的perf record会直接报错。# 查看当前限制 cat /proc/sys/kernel/perf_event_paranoid # 临时允许普通用户使用 perf重启失效 sysctl -w kernel.perf_event_paranoid-1-1意味完全不限制0允许用户态采样1允许内核态采样但需要 root。线上安全起见一般建议设成1而不是-1。容器里如果用的是默认 seccomp 配置也可能阻止 ptrace导致 strace 无法正常工作。这时可以在部署 YAML 里给容器加SYS_PTRACE权限或者在宿主机上直接对容器进程做 trace。这套组合拳放在生产环境时我建议先做一次演练。在测试环境用相同镜像起一个服务模拟压测并发把 perf 和 strace 都跑一遍观察对服务的延迟影响。这样到了线上真正动手时你对“挂 30 秒 perf 会让 P99 增加多少”是有心理预期的不会出现工具上了但业务延迟飙到用户不可接受的惨剧。6. 实操心得与后续扩展你可能会说perf和strace命令就那几个看火焰图不就行了为什么要花这么大篇幅讲思路。实际上线上排障真正难的不是敲命令而是数据的解释和决策。这里再分享几个踩过的坑。第一个心得采样数据要有足够的代表性也要控制好时间。有一次我对一个低峰期的服务采样因为流量本身较低火焰图热点全是后台定时任务白忙一场。后来我都会先看一眼当前 QPS 再决定采样窗口最好选在业务高峰期或者压测时间段。如果无法在高峰期操作那就把采样时间拉长到 2~3 分钟争取覆盖足够多的业务请求。第二个心得日志里的异常并不一定是根因也可能是结果。比如你看到线程池拒绝任务的日志第一反应是调大线程池但真实原因可能是某个下游连接池被打满阻塞了所有线程。用 strace 看一下线程到底阻塞在哪往往比直接调参数更接近真相。调整参数只是延缓症状定位到阻塞点才是治疗病根。第三个心得perf 火焰图看的是 CPU如果瓶颈在 IO 或锁等待CPU 火焰图会骗你。所以一定要结合 strace 的等待信息两者不可偏废。曾经我试过只依赖火焰图看到热点是日志库的字符串拼接于是疯狂优化字符串拼接但 P99 几乎没有变化。后面用 strace 才发现线程大量阻塞在fsync上每次滚动日志都要强制刷盘这才是瓶颈。优化落盘策略后延迟肉眼可见地下降。后续如果要进一步提升这套流程的效率还可以把常用命令封装成脚本比如一键收集 perf.data、火焰图 SVG、strace 摘要、线程栈 dump全部打包成一个压缩包方便复盘和团队协作。我在公司内部就用 Shell 脚本把这些步骤固化成perf-toolkit遇到线上问题直接跑一次几分钟就能把数据收集齐极大地降低了排障的心理门槛。最后再分享一个小技巧在跑 perf 之前如果感觉 CPU 使用率来自 JVM 的 JIT 编译线程可以用-F适当调低采样频率或者过滤掉 JVM 内部符号。这里的核心逻辑不是躲避问题而是让热点更容易暴露出来不至于被 JIT 相关符号淹没。生产环境的稳定性永远是第一位的宁可多采集几轮也不要为了少跑一次采样而让服务冒风险。
