生产环境CPU飙高不用慌:3条命令定位Java线程代码行号
半夜两点被电话吵醒生产环境的告警群里已经炸了锅核心服务的 CPU 使用率直接飙到 100%接口大量超时用户开始投诉。这种场景干过生产运维的人都不陌生尤其是第一次遇到的时候很多人第一反应就是登录服务器翻日志、看监控曲线、查慢查询一通操作下来半小时过去了CPU 还是红的问题也没定位到。我在生产环境摸爬滚打这些年经历过好几轮类似的“火情”到后来总结出一个非常直接的排查套路不需要翻大量日志也不需要复杂的 profiling 工具就靠 3 条命令最快 1 分钟就能把 CPU 飙高的线程对应的代码行号揪出来。这套方法在 Java 技术栈的生产环境里尤其好用对 K8s 容器环境同样适用。先说清楚这套方法的核心思路就三步先用top找到 CPU 占用最高的 Java 进程 PID再用top -Hp找到进程内部 CPU 占用最高的线程 TID最后用jstack导出线程堆栈把 TID 转成十六进制的 nid 去匹配堆栈里打印的就是当前线程正在执行的代码调用链代码行号清清楚楚。这篇文章我会把每一步的原理、命令参数、实际操作中会踩的坑、以及 K8s 容器环境下要注意的细节全部拆开讲透保证你看完能直接照着操作下次再遇到 CPU 飙升不用再慌慌张张翻日志。1. 先搞清楚 CPU 飙升的本质别急着动手先分辨几种情况很多人在 CPU 飙高的时候容易犯一个错误一上来就抓线程、导堆栈结果抓到的堆栈五花八门反而看花了眼。实际上 CPU 飙升这件事原因可以分成几类每一类的排查路径完全不同第一步的判断决定了后续方向对不对。1.1 用户态 CPU、内核态 CPU、等待 IO三种情况性质完全不同通过top命令看到 CPU 飙到 100%这只是表象。我们得先在脑子里过一遍CPU 时间到底花在哪了Linux 的 CPU 使用率分为用户态us、内核态sy、等待 IOwa、硬中断hi、软中断si等几个部分其中最常见的是前三种。用户态 CPU 高说明应用程序自己的代码在大量消耗 CPU 计算资源典型场景是死循环、频繁的 GC、大量的序列化/反序列化、正则匹配、加密计算等。这是 Java 应用最常见的 CPU 飙升原因也是这篇文章要重点解决的场景。内核态 CPU 高说明程序在频繁调用系统调用或者内核层面出现了问题。比如线程频繁地进行锁竞争、文件读写、网络收发导致上下文切换过高或者系统本身有软中断风暴。遇到这种情况单纯抓 Java 线程堆栈往往看不出问题需要结合vmstat、sar等工具看系统层面指标。等待 IO 高CPU 在等待磁盘、网络等外设响应应用本身并没有在“算”而是在“等”。这种情况常见于磁盘性能瓶颈或者存储故障iostat是主要排查工具抓 Java 堆栈帮助也不大。判断方法很简单登录服务器执行一次top看第三行CPU 状态行的 us、sy、wa 三个值。如果 us 高走 Java 线程堆栈的路线如果 sy 高重点查上下文切换和锁竞争如果 wa 高查磁盘 IO。我用这个办法过滤掉了大量无效排查时间因为很多新手在 wa 高的情况下依然花半小时抓 Java 堆栈方向错了怎么查都是白费。1.2 应用层原因最常见的三类死循环、频繁 GC、线程锁竞争在确认是用户态 CPU 高之后进一步缩小范围Java 应用层常见的 CPU 飚高原因也就那几类。第一类是业务代码死循环。比如while循环的退出条件永远不满足、for循环的步进逻辑写错、在递归里没有设置终止条件等。这类问题最典型的特点就是某个线程长期霸占 CPU堆栈上能看到一个非常“干净”的调用链——就那么几个方法反复出现线程状态是RUNNABLE。第二类是频繁 GC。内存快满了又没满JVM 不停地做 Minor GC 甚至 Full GCGC 线程会消耗大量 CPU。这类问题的特点是 CPU 使用率呈现锯齿状波动同时 GC 日志里能看到频繁的 GC 记录堆栈抓出来会看到很多VM Thread或者垃圾回收相关的线程在活动。第三类是线程锁竞争激烈。大量线程在等待同一个锁锁的获取和释放频繁触发操作系统层面的上下文切换和系统调用导致内核态 CPU 偏高同时应用的整体吞吐量下降。堆栈里会看到大量线程停在park或waiting for monitor entry状态。我之所以先花篇幅讲这些是因为后面的 3 行命令虽然快但它并不是万能药。它主要解决第一类“业务线程跑死循环或者热点代码”的问题对 GC 和锁竞争也有辅助诊断作用但你得先看懂堆栈才能对症下药。这个认知很重要不然你抓了一堆堆栈也不知道自己在看什么。2. 三行命令定位 CPU 飙高的完整操作流程核心流程先放这后面逐条拆解原理和参数。整套操作在标准 Linux 环境下复制粘贴就能执行前提是 JDK 的jstack命令可用且当前用户对目标进程有权限一般是和 Java 进程相同的用户比如root或应用账号。# 第 1 行找出 CPU 占用最高的 Java 进程 PID top -c # 第 2 行查看该进程内 CPU 占用最高的线程 TID top -Hp PID # 第 3 行将线程 TID 转成十六进制 nid并用 jstack 查找对应线程堆栈 jstack PID | grep -A 50 nid0x十六进制线程号单看命令就这么简单但要把每一步做对尤其是第 3 步里那个十六进制转换有不少细节值得展开。2.1 第 1 步用top -c锁定占用 CPU 最高的进程登录服务器后第一件事就是执行top。为什么要加-c因为默认的top只显示进程名像 Java 进程通常只显示一个java多个 Java 应用部署在同一台机器上时你根本分不清是哪个应用出了问题。加上-c之后top会显示完整的命令行参数也就是我们启动 Java 时指定的-Dspring.application.namexxx、-jar xxx.jar等信息能快速识别是哪个服务。操作细节上我建议直接执行top -c后按一下数字键1展开每颗 CPU 核心的使用情况确认是所有核心都跑满还是只有个别核心跑满。如果是单颗核心跑满通常是某个线程死循环如果是整体跑满叠加多个线程或 GC 的可能性更大。看到目标 PID 后按P键让进程按 CPU 使用率排序这样最耗 CPU 的进程一定排在最上面。这一步通常几秒钟就能完成。另外如果你所在的环境是 K8s 容器直接看宿主机上的top其实也能看到容器进程但 PID 是宿主机视角的 PID不是容器内的 PID后面用jstack的时候要注意路径差异。更推荐的做法是在容器内直接执行top但很多精简镜像没有安装top这种情况下可以用cat /proc/cpuinfo等替代手段或者直接在宿主机用top -c找到容器对应的进程这部分我会在第 4 节专门讲。2.2 第 2 步用top -Hp PID定位到具体线程拿到了进程 PID 之后执行top -Hp PID-H表示以线程模式显示-p指定进程号。这时top输出的就不再是进程概览而是该进程内部所有线程的 CPU 占用情况。继续按P键按 CPU 排序排在最上面的那个线程就是罪魁祸首。这一步要留意的列是 PID 列——在-H模式下top的 PID 列显示的其实是线程 IDTID也就是内核视角的轻量级进程 IDLWP。这个 TID 是十进制的我们需要把它记下来。比如我现在看到占用 CPU 最高的线程 TID 是31245那就把它记下来下一步就要用到。这里有一个非常实用的经验如果一次抓到的堆栈里最高 CPU 线程不明显或者线程经常变化说明 CPU 飙升可能不是单线程死循环而是多个线程轮流抢占 CPU或者是 GC 频繁触发的表现。遇到这种情况多抓几次top -Hp看线程切换的规律同时配合jstat -gcutil PID 1000看 GC 频率往往能更快锁定本质原因。2.3 第 3 步printf转换加jstack匹配一行命令直接看到代码行号最核心的一步来了Java 线程堆栈里的线程标识是以十六进制nid形式存在的比如nid0x7a15。而我们从top -Hp拿到的是十进制 TID必须先把十进制转成十六进制才能在jstack输出中准确匹配到目标线程。转换命令一句话printf %x\n 3124531245 转成十六进制是7a15实际上printf %x\n 31245输出的就是十六进制值。这个十六进制值必须补0x前缀去匹配也就是我们要在jstack输出里找nid0x7a15。那条线程的完整堆栈就是 CPU 飙升的直接原因。然后执行jstack PID | grep -A 50 nid0x7a15-A 50表示匹配到之后额外打印 50 行实际中 50 行通常足够看完整条调用链。堆栈输出里会有三块关键信息线程名、线程状态RUNNABLE、WAITING、BLOCKED等、以及完整的 Java 调用栈。从栈顶到栈底就是线程从当前正在执行的方法到最外层调用方的完整路径最上面那一两行的类名和行号就是正在消耗 CPU 的代码位置。我见过不少同事在这一步卡住原因很统一转换十六进制的时候自己拿计算器按或者把大小写写错了。printf输出的小写字母 x 对应的%x匹配的时候堆栈里的 nid 一般也是小写但保险起见建议把十六进制数字直接复制进命令里避免手输出错。3. 为什么 jstack 堆栈能定位到代码行号原理讲透很多人用jstack用了很多年都是“背命令”的状态并不知道它背后到底做了什么。其实理解了原理你才有可能在异常场景下灵活变通比如堆栈抓不到、线程状态异常、GC 线程干扰等情况下做出正确判断。这一节我把原理拆成两层来讲第一层是 jstack 怎么抓到线程堆栈第二层是怎么把堆栈信息对应到代码行号。3.1 jstack 的工作原理线程快照和调用栈回溯jstack是 JDK 自带的命令行工具作用是对指定 Java 进程生成线程快照thread dump。它通过 JVM 的线程管理和调试接口把当前所有存活线程的状态、调用栈、锁信息等一次性输出到标准输出或指定文件。调用栈回溯的原理可以理解为每个线程在执行 Java 方法时JVM 都会维护一个栈帧Stack Frame里面记录了方法调用关系、局部变量、操作数栈等信息。jstack发起线程快照时会请求 JVM 暂停目标线程实际上是请求线程在安全点Safe Point处暂停然后逐帧读取当前执行位置把这些信息翻译成我们看到的类名、方法名、行号。这个行号信息是编译期间就写入 class 文件字节码的调试信息里的也就是-g编译选项默认开启产生的 LineNumberTable。所以这里有一个很关键的前提你的代码在编译打包时不能使用-g:none之类的参数禁用调试信息否则行号就无法显示。绝大多数 Maven/Gradle 默认配置都是保留调试信息的所以正常情况下能直接看到.java:123这样的行号。3.2 怎么把堆栈里的类名、行号对应到实际代码拿一个典型的堆栈来说http-nio-8080-exec-10 #23 daemon prio5 os_prio0 cpu1234.56ms elapsed300.22s tid0x00007f8b24012800 nid0x7a15 runnable [0x00007f8b1e7fe000] java.lang.Thread.State: RUNNABLE at com.example.order.service.OrderService.calculatePrice(OrderService.java:88) at com.example.order.controller.OrderController.submit(OrderController.java:45) at com.example.order.config.GlobalFilter.doFilter(GlobalFilter.java:23) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ...OrderService.java:88就是当前线程正在执行的具体代码位置。拿到这个信息之后我一般会先做两件事打开对应的源码文件看第 88 行附近是什么逻辑同时把这条堆栈复制下来保存方便后续复盘。再看堆栈底部通常是框架的入口比如 Tomcat 的线程池执行器、Spring 的调度器等这说明这条业务请求是从哪里进来的。这里要特别强调一个容易误判的点堆栈顶部的RUNNABLE状态不等于正在执行 Java 代码。如果线程正在执行 JNI 调用或者等待某种系统资源堆栈顶部可能会停在native方法上此时行号标识可能是-1或者显示为 native 方法名称。这种情况下说明 CPU 消耗发生在 native 层要结合其他手段排查。3.3 为什么线程名和 nid 要同时看区分 Tomcat 工作线程和 JVM 内部线程堆栈输出第一行的tid和nid的含义要分清tid是 JVM 内部的线程对象 ID十进制nid是操作系统层面的线程 ID十六进制也就是top -Hp里看到的那个 TID 的十六进制形式。两者存在对应关系但数值不同我们用top -Hp抓到的是 nid 的前身所以匹配时必须转成十六进制。另外堆栈里的线程名信息同样值得关注。常见的线程名有http-nio-8080-exec-xxTomcat 工作线程、scheduling-1Spring 定时任务线程、main主线程、GC Thread垃圾回收线程、VM ThreadJVM 内部线程等。如果你发现高 CPU 线程是GC Thread或VM Thread那问题大概率不是业务代码死循环而是 JVM 自身在疯狂 GC这时候应该去看堆内存使用情况和 GC 日志而不是盯着业务堆栈分析。我把这条经验放在这是因为很多人在第一次抓堆栈时会看到 GC 线程不知道怎么处理白白浪费时间。4. 实战案例一段真实的生产排障过程记录光讲命令和原理比较干我拿一个发生过的真实案例来演示完整过程。背景是一个订单服务在某次大促预热期间 CPU 突然飙到 100%接口 P99 延迟从 50ms 涨到了 5 秒部分节点开始出现 OOM 风险。当时值班同事已经在翻日志了但一时半会没看出异常我接手后用这三条命令在几分钟内就锁定了问题代码。4.1 现场操作记录从 top 到 jstack 的每一步输出登录跳板机后执行top -c输出大概长这样top - 14:22:31 up 12 days, 3:22, 3 users, load average: 4.12, 3.89, 3.56 Tasks: 356 total, 1 running, 355 sleeping %Cpu(s): 78.3 us, 12.5 sy, 0.0 wa, 8.5 id ... PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND 19231 root 20 0 11.6g 2.4g 32m R 198.7 2.1 345:23.02 java -Xms2g -Xmx2g -Dspring.application.nameorder-service -jar app.jar看到%CPU接近 200%说明占满了 2 个核心进程名明确显示是order-servicePID 19231。同服务器上还有其他 Java 进程但 CPU 占用都很低基本可以锁定就是它。接着执行top -Hp 19231按P排序后PID USER PR NI VIRT RES SHR S %CPU MEM TIME COMMAND 19887 root 20 0 11.6g 2.4g 32m R 95.2 2.1 320:55.02 java 19895 root 20 0 11.6g 2.4g 32m R 85.7 2.1 217:33.19 java有两个线程都在高 CPU 运行TID 19887 和 19895。分别转十六进制printf %x\n 19887 # 输出 4daf printf %x\n 19895 # 输出 4db7然后执行 jstack 匹配jstack 19231 | grep -A 50 nid0x4daf4.2 堆栈分析问题代码和根因复盘匹配到的堆栈关键部分如下http-nio-8080-exec-12 #46 daemon prio5 os_prio0 cpu31002.12ms elapsed302.51s tid0x00007f8b24012800 nid0x4daf runnable [0x00007f8b1e7fe000] java.lang.Thread.State: RUNNABLE at java.util.regex.Pattern$BmpCharIterator.next(Pattern.java:4753) at java.util.regex.Pattern$BmpCharIterator.match(Pattern.java:4762) at java.util.regex.Matcher.find(Matcher.java:709) at java.util.regex.Matcher.matches(Matcher.java:636) at com.example.order.service.OrderService.parseAddress(OrderService.java:156) at com.example.order.service.OrderService.calculatePrice(OrderService.java:88) at com.example.order.controller.OrderController.submit(OrderController.java:45)堆栈很清晰地告诉我们线程正在执行OrderService.parseAddress这个方法里的正则匹配而且花在java.util.regex上的 CPU 时间已经有 310 秒。打开OrderService.java第 156 行发现了问题代码一个用于校验手机号的Pattern.matches调用正则表达式本身没问题但它被放在了一个for循环里每次订单请求都要对大段字符串做一次完整正则匹配。在大促流量下这个本来不算慢的操作被放大成了性能瓶颈。根因复盘后修复方式有两种一是把正则Pattern编译提升为静态常量避免每次匹配都重新编译二是用更轻量的字符串判断逻辑替代正则。最终我们选择了静态 Pattern 的方式修复后同样的压测流量下 CPU 降到 15% 左右P99 延迟回到 60ms。这个案例也验证了一个经验生产环境 CPU 飙高有时候不是代码逻辑复杂而是低效操作在并发量上来之后被放大了。4.3 一次抓不到堆栈怎么办jstack 执行失败的三种典型情况实战中jstack并不是每次都能顺利执行尤其是容器环境或高负载场景下常见有这么几种情况。第一种是报Unable to open socket file或者well-known file is not secure这通常是因为执行jstack的用户和启动 Java 进程的用户不一致或者java进程的临时目录权限有问题。解决方法是用启动 Java 的用户执行 jstack或者加上sudo -u 应用用户前缀。第二种是jstack命令直接卡住不动高 CPU 或高 GC 情况下JVM 可能处于“假死”状态线程快照请求迟迟得不到响应。这时可以加-F强制模式jstack -F PID强制使用附着机制抓取堆栈但-F在部分 JDK 版本上输出格式略有差异nid 匹配方式不变。第三种是提示Unable to attach或者attach超时这在容器里比较常见因为容器内/proc挂载权限受限或者缺少/tmp目录访问权限导致 attach 机制失效。这种情况可以改用jcmd PID Thread.print替代它和 jstack 的输出格式基本一致同样可以用nid去匹配线程。5. 容器和 K8s 环境下的变通技巧现在大部分新业务都跑在 K8s 里直接套用传统虚拟机上的命令流程会遇到几个坑我单独拿出来讲。核心原则是能进容器执行的就进容器执行容器内没有工具就去宿主机找线索。5.1 在 K8s Pod 内执行命令失败时怎么处理很多基础镜像为了精简体积只包含运行 JDK 和必要的库没有top、没有jstack、甚至没有bash。遇到jstack: command not found时不要慌有几个替代路径。如果你用的是带 JDK 的镜像而不是只有 JREjstack一般位于 JDK 的bin目录下镜像里没加 PATH 的话可以全路径执行比如/usr/local/jdk/bin/jstack PID。实在不行还可以利用 JDK 自带的jcmd它能完成 jstack 的大部分能力jcmd PID Thread.print。如果容器里连top都没有可以用cat /proc/PID/status查看线程状态用ps -eLf查看线程列表有些镜像也没有ps。最稳妥的办法是在宿主机上找到容器进程。先用crictl ps或docker ps找到容器 ID然后根据容器 ID 反查它在宿主机上的 PID再在宿主机上用top -Hp和jstack执行排障。这时候要记住容器内的 Java 进程 PID 通常是 1如果镜像直接以 java 命令为 ENTRYPOINT而宿主机视角下对应的是另一个 PIDjstack要用宿主机的 PID 执行。5.2 宿主机视角和容器视角的 PID 映射关系具体操作步骤我列出来遇到容器环境可以直接参考在宿主机上执行crictl psK8s 一般用 containerd或docker ps找到目标容器 ID。执行crictl inspect containerId或docker inspect containerId在输出里找Pid字段这就是容器主进程在宿主机上的 PID。宿主机的top -c里看到的 Java 进程PID 就是那个 Pid 值。直接对宿主机 PID 执行top -Hp和jstack实际操作对象其实是容器内的同一个 Java 进程。有一个 JBOD 经验容器内执行jstack经常遇到Unable to attach原因是容器缺少SYS_PTRACE权限或者/proc被限制。解决方案是给 Pod 加CAP_SYS_PTRACE权限或者在部署时给 Java 进程容器增加shareProcessNamespace: true。不过这些属于基础设施侧的调整线上排障时最省事的方式还是宿主机操作。5.3 服务网格和 sidecar 场景下的 CPU 归属问题K8s 里如果使用了服务网格比如 IstioPod 内会同时运行业务容器和 envoy sidecar 容器。有时候业务容器 CPU 正常但 sidecar 容器把整个 Pod 的 CPU 打满了从应用堆栈完全看不出原因。判断方法是在宿主机上用top -c看进程命令行envoy 进程的命令行里会包含envoy字样CPU 占用极高的话问题就出在网络代理层而不是业务代码。遇到这种问题再去抓 Java 堆栈没有意义应该检查 sidecar 的 CPU limit 配置、istio-proxy 的连接数、日志中的 upstream 超时和重试风暴等。我在实际踩坑中发现很多所谓“CPU 飙升到 100%”的告警最后根因是大量请求因为下游服务变慢导致 sidecar 里的重试和连接创建反复执行把 CPU 消耗在了网络协议栈上。所以说三步法定位 Java 代码的前提是你得先确认 CPU 确实是业务进程消耗的。这个判断用top -c第一眼就能看出来千万不能跳过。6. 配套工具和辅助命令让定位更快的几个实用组合三行命令是核心但实际排障中配合几个辅助命令效率还能再上一个台阶。这一节整理几个高频组合每一组都能解决一个特定场景。6.1 vmstat 和 jstat判断 GC 和上下文切换是否异常vmstat 1 5可以看到系统的运行队列、上下文切换次数cs列、CPU 的 us/sy 比例等关键指标。如果cs列数值很高比如超过几万说明系统在频繁切换线程这时候即使 Java 堆栈上线程看起来都正常性能也可能很差。这是排查内核态 CPU 高的首选命令。jstat -gcutil PID 1000 5可以查看 JVM 堆内存的使用率和 GC 时间。重点看FGCFull GC 次数和FGCTFull GC 累计时间如果 FGC 频繁且 FGCT 持续上涨说明内存压力很大需要优先排查堆配置、对象分配速率和内存泄漏风险。我经常遇到的情况是 CPU 飙高同时 FGC 次数大幅增加这种双重告警的根因往往是内存中某个大集合越积越大触发频繁 Full GC而 GC 线程又把 CPU 吃掉了。6.2 重复抓堆栈判断线程是否持续“钉”在同一个位置一次 jstack 只能说明某个瞬间线程在执行什么代码但 CPU 飙升往往需要确认这个线程是否长时间停留在一个地方。我的做法是隔 5 到 10 秒连续抓两三次 jstack对比目标线程的堆栈是否一致。如果三次堆栈的栈顶都一样基本可以断定这是一个死循环或者长期热点当前方法就是问题所在。如果三次堆栈各不相同线程像“打游击”一样在多个方法间跳动那可能是任务调度造成的整体负载过高或者多个线程在竞争共享资源需要进一步分析。jstack PID /tmp/thread_dump_1.txt sleep 10 jstack PID /tmp/thread_dump_2.txt diff /tmp/thread_dump_1.txt /tmp/thread_dump_2.txtdiff的结果里目标线程区域如果没有变化那几乎就是实锤了。这个方法也适合压制“误报”有时候偶尔一次的 CPU 毛刺并不是真实问题连续抓堆栈可以有效判断问题的持续性。6.3 用 Arthas 在线诊断作为补充方案如果你所在的环境允许引入诊断工具阿里开源的 Arthas 在 CPU 定位上有更直观的能力。Arthas 的thread命令可以直接按 CPU 占用排序显示线程甚至可以一键导出 CPU 使用率最高的线程堆栈省去了手动转换十六进制的步骤。# 进入 Arthas 交互界面后执行 thread -n 3这条命令会列出 CPU 占比最高的 3 条线程并自动附带堆栈信息对不熟悉printf转换的同事来说非常友好。不过 Arthas 在部分生产环境有限制比如需要网络下载、需要额外的端口暴露或者安全审计不允许部署 agent所以我还是建议先把原生 JDK 工具链练熟。原生工具在任何环境都能用Arthas 是锦上添花。7. 常见问题与排查技巧实录从经验出发我把操作过程中见过的高频问题整理成了一张速查表并且补充一些常规文档里不会写的实战细节方便你遇到具体报错时快速对照。7.1 高频问题速查jstack 报错、线程状态和十六进制转换问题现象可能原因解决思路jstack: not found使用 JRE 镜像未包含 JDK 工具使用全路径 JDK 命令或进宿主机操作Unable to open socket file执行用户与 Java 进程用户不一致切换为启动用户执行或加 sudoUnable to attach超时容器缺少 ptrace 权限或 /proc 限制宿主机执行 jstack或给 Pod 加权限top -Hp看不到任何线程非 Java 进程或权限不足检查 PID 是否为 java 进程改用 root 执行堆栈中 nid 一直匹配不上十六进制转换错误或匹配了非目标线程用 printf 重新计算并复制输出注意大小写栈顶显示 native 方法线程卡在 JNI 或系统调用结合 vmstat、strace 分析系统层行为这份表格是我排查时最常用的备忘录。特别是 nid 匹配不上的情况往往不是命令执行错误而是top -Hp显示的线程 ID 在抓堆栈的瞬间线程已经退出了比如线程池里的临时线程生命周期很短这种时候需要多抓几次或者用线程名模糊匹配。7.2 线程状态 BLOCKED/WAITING 怎么解读堆栈输出里java.lang.Thread.State一栏是判断线程行为的重要依据但它的含义经常被误解。我简单梳理一下RUNNABLE线程正在 JVM 中执行可能正在运行 Java 代码也可能正在等待操作系统调度。CPU 飙高时RUNNABLE 且栈顶是业务代码的线程最可疑。WAITING/TIMED_WAITING线程在等待某个条件典型调用是Object.wait()、LockSupport.park()等。大量线程处于这个状态说明系统在等待资源但如果锁的持有者迟迟不释放等待线程会排队间接导致吞吐量下降。BLOCKED线程在等待进入同步块或锁的 monitor。大量 BLOCKED 意味着严重锁竞争配合堆栈底部的- waiting to lock 0x...和- locked 0x...信息可以追踪到锁对象和持锁线程。一个常见误区是看到大量线程WAITING就觉得系统出问题了。实际上线程池里的空闲线程常态就是 WAITING关键在于高 CPU 的同时是否伴随大量 BLOCKED 或异常堆积。CPU 高 线程多处于 RUNNABLE是死循环或热点代码的典型组合CPU 高 线程大量 BLOCKED则锁竞争和上下文切换才是主因。7.3 给新人的三个避坑建议和两条保命经验最后分享几条实打实的经验都是我踩过的坑换来的。避坑建议一不要在 CPU 刚飙高时立刻一顿操作猛抓堆栈。先花 30 秒看一眼 system load、GC、磁盘 IO确认 CPU 是否持续高占用。如果是持续性的再走三步法如果是偶发毛刺连续抓堆栈对比会更靠谱。避坑建议二保存现场。抓到的jstack输出、top截图、vmstat记录全部按时间点归档。生产事故复盘时这些现场数据比事后你的回忆可靠得多排查团队协作时也能统一事实基础。避坑建议三不要把jstack执行频率提得太高。在高并发生产环境jstack 本身也有短暂停顿线程的开销频繁执行可能加剧服务抖动。间隔 5 秒以上的两次快照足够判断问题不要为了追求“多次采样”而用每秒一次的频率去抓我在实测中发现这样会对线上造成额外影响。保命经验一生产环境改代码前先确认每一个可疑栈帧的含义。我看到过有同事拿着堆栈上的一行类名就去改代码结果改完发现那只是框架的通用调用真正的业务热点在栈帧的更深处。正确做法是从栈顶往栈底逐帧读懂明确每个方法在业务链路中的位置再下结论。保命经验二如果堆栈里能看到Unknown Source先去确认编译配置。这个提示说明当前 jar 包缺少调试行号信息大概率是构建脚本里对javac使用了-g:none或用 ProGuard 混淆过。这种情况下行号定位失效需要重新打包或者结合反编译工具人工比对定位效率会打折扣。8. 这套流程还能怎么扩展从定位问题到自动化兜底三步法解决的是“当时当刻”的故障定位但在大规模集群里不可能每次 CPU 飙升都靠人肉登录服务器执行命令。我自己的实践是把这套流程固化成了可重复执行的脚本配合监控告警做初步自动诊断能大幅降低“半夜被叫醒”的次数。一个轻量级的思路是写一个 shell 脚本接收 PID 参数自动执行 top 截取、线程排序、jstack 抓取和 nid 匹配把结果输出到文件并附带时间戳。脚本本身不复杂但它能让值班同学在任何一台服务器上无脑执行减少了临场记忆命令的成本。更进一步可以在告警回调里直接触发脚本把输出结果推送到群或工单人还没登录服务器问题原因已经分析好了一半。如果你所在团队有 APM 或可观测平台还可以把线程堆栈、CPU 使用率、GC 指标关联成一条时间线。这样下一次 CPU 飙高时平台自动告诉我们“哪个服务的哪个线程在某条代码上持续消耗 CPU”三步法就从人工操作演变成了系统能力。当然这些属于投入产出比更高的长期建设不过就算没有平台学会这三条原生命令也不吃亏。我个人在实际操作中的体会是排障最怕的不是故障本身而是毫无章法的乱试。先看方向、再抓线程、最后读堆栈这个顺序比任何花哨的工具都重要。而且你会发现CPU 定位练熟之后你读代码和做性能优化的敏感度也会跟着提升因为在堆栈里反复出现的热点往往就是代码里性能最差的那些地方值得你在大促前重点优化。

相关新闻

Meshy+Chrona:语义驱动的AI 3D协作新范式

Meshy+Chrona:语义驱动的AI 3D协作新范式

1. 这不是“AI画图3D建模”的简单叠加,而是一次协作范式的迁移最近两周,我连续参加了三场线上3D协作测试,其中最让我坐直身体的是Meshy和Chrona联合发起的这场活动。它不叫“AI生成3D模型大赛”,也不叫“多人建模挑战赛”&#xf…

2026/10/1 11:52:27 阅读更多 →
Mac mini本地AI工作流实战:普通人可用的稳定生产力方案

Mac mini本地AI工作流实战:普通人可用的稳定生产力方案

1. 别被“AI时代”四个字吓住:Mac mini不是玩具,是普通人最现实的AI生产力入口很多人看到“AI时代”就下意识觉得得配RTX 4090、租A100集群、学PyTorch源码——这恰恰是信息过载时代最大的认知陷阱。我用Mac mini M2(2023款,16GB统…

2026/10/1 11:52:27 阅读更多 →
HER算法拆解:用后见之明解决强化学习稀疏奖励难题

HER算法拆解:用后见之明解决强化学习稀疏奖励难题

hindsight,后见之明。如果你最近在折腾强化学习,尤其碰过那种“死活等不到正奖励”的稀疏奖励任务,这个词你绕不开。这里说的不是什么“早知道我当初就……”的人生感慨,而是 OpenAI 在 2017 年开源的一套经典到不能再经典的算法—…

2026/10/1 11:51:26 阅读更多 →

最新新闻

摩尔线程社区版驱动v240.50.0.1开放HDR支持:完整设置与问题排查

摩尔线程社区版驱动v240.50.0.1开放HDR支持:完整设置与问题排查

1. 这次社区版驱动更新的核心:HDR 支持开放的含金量摩尔线程把社区版驱动推到了 v240.50.0.1,这几天显卡群和评测圈里讨论最多的就是这一版正式开放了 HDR 支持。对用 MTT S80、S70 这类桌面卡的朋友来说,这算是一个盼了挺久的功能&#xff1…

2026/10/1 12:26:48 阅读更多 →
Spring Boot 4.2-M2 带着新基线来了:这次卡住升级的不是代码,是配置

Spring Boot 4.2-M2 带着新基线来了:这次卡住升级的不是代码,是配置

升级到 Spring Boot 4 的第一个下午,最常见的报错长这样:应用起不来,日志里只有一句——某个配置属性绑定失败,原因是找不到对应的类。Java 代码一行没动,卡住是 application.yml 里的一个键。这类问题在 Boot 4 的迁移…

2026/10/1 12:26:48 阅读更多 →
宝塔面板SSL证书配置与HTTPS不安全提示排查实战

宝塔面板SSL证书配置与HTTPS不安全提示排查实战

建站的朋友应该都遇到过这个场景:网站后台明明配好了SSL证书,HTTPS也能正常打开,但浏览器地址栏就是固执地显示“不安全”,或者干脆一把大红锁,怎么看怎么闹心。尤其用宝塔面板部署站点时,这类问题更常见&a…

2026/10/1 12:26:48 阅读更多 →
Cloudflare安全审计技能7天狂揽1.3万星、阿里开源代码评审冲上热榜第一:AI写的代码谁来把关

Cloudflare安全审计技能7天狂揽1.3万星、阿里开源代码评审冲上热榜第一:AI写的代码谁来把关

掘金GitHub Trending周报显示,9月中旬Cloudflare开源的security-audit-skill登顶日榜,一周斩获13,704个星标;阿里open-code-review同样冲上日榜第1,总星数达36,678。当AI编程代理从写代码扩展到审代码,一个新问题摆上桌…

2026/10/1 12:26:48 阅读更多 →
基于Hadoop+Spark+Hive的游戏推荐系统设计与实现

基于Hadoop+Spark+Hive的游戏推荐系统设计与实现

看到“HadoopSparkHive游戏推荐系统”这个题目,我的第一反应是:这是个能把本科大数据技术栈一网打尽的题目。它不像纯算法题那么飘,也不像纯网页开发那样缺少大数据味道。你要在一台机器或一个小集群上,把数据采集、分布式存储、数…

2026/10/1 12:26:48 阅读更多 →
MMC整流FCS-MPC控制Simulink仿真复现:从建模到排坑指南

MMC整流FCS-MPC控制Simulink仿真复现:从建模到排坑指南

最近刚好在复现一篇SCI二区IEEE上关于模块化多电平换流器(MMC)整流电路的FCS-MPC控制方案,用的是Simulink仿真。整个流程从看论文、搭模型、写控制算法到调试、排坑,前前后后折腾了不少时间。这篇博文就围绕这个项目,把…

2026/10/1 12:25:48 阅读更多 →

日新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/30 18:13:06 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 0:00:30 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/1 1:01:17 阅读更多 →