先用top -c查出高cpu的java进程pid,再用top -hp pid定位高cpu线程tid,接着printf "%x\n" tid转十六进制,最后jstack pid | grep 十六进制tid -a10定位问题代码行。

top 找出高 CPU 的 Java 进程 PID
CPU 飙升时,top 是第一道筛子。别等报警邮件发完才动手,直接连上机器敲:top -c。重点看 %CPU 列——排第一的 java 进程大概率就是目标。注意:多核系统中可能显示 199.3、298.1 这类超 100% 的值,这是正常的,它表示占用了多个 CPU 核心的总时间。
记下它的 PID(比如 12345),这一步不难,但容易忽略 -c 参数。没加的话,COMMAND 列只显示 java,看不到具体 jar 包或启动参数,后续无法确认是不是你要查的服务。
top -Hp 查线程级 CPU 占用
一个 Java 进程里有成百上千个线程,top 只能定位到进程层。真正“打满 CPU”的,往往只是其中一两个线程。执行:top -Hp 12345(把 12345 换成你上步拿到的 PID)。
这时界面列出的是线程(TID),不是进程。再盯 %CPU 列——出现 99.5、98.7 这种接近 100% 的线程 ID(比如 12367),就是关键线索。注意:别被 S(Sleeping)状态迷惑,R(Running)或 Runnable 状态才真正消耗 CPU。
- 如果多个线程都高 CPU,说明可能是并发死循环或密集计算任务,要逐个查
- 如果只有 1 个线程长期 99%+,大概率是单点死循环(比如
while(true)或分页 size=0 导致无限翻页)
printf "%x" 把 TID 转成十六进制
jstack 输出的线程 ID 字段叫 nid,格式是 nid=0x304f 这样的十六进制。而 top -Hp 给你的是十进制 TID(比如 12367)。不转就对不上,grep 会空手而归。
执行:printf "%x\n" 12367,输出 304f。记住这个结果,后面要用——它等价于 0x304f,但 grep 时不用写 0x 前缀。
这步最容易卡住:有人手动计算器转换,出错率高;有人漏掉 \n 导致输出带空格,grep 匹配失败;还有人把进程 PID 错当线程 TID 去转,白忙活。
jstack | grep 定位到具体代码行
有了十六进制线程 ID(比如 304f),就能从堆栈里揪出问题代码了。执行:jstack 12345 | grep "304f" -A 10 -B 2(-A 10 表示向下取 10 行,-B 2 向上取 2 行,确保看到完整堆栈帧)。
输出里会有一段类似:
"http-nio-8080-exec-2" #30 daemon prio=5 os_prio=0 tid=0x00007f871806e000 nid=0x304f runnable [0x00007f870c1b0000]
java.lang.Thread.State: RUNNABLE
at com.example.api.LoopController.loop(LoopController.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
关键就在 at com.example.api.LoopController.loop(LoopController.java:18) 这一行——它直接指向源码第 18 行。常见陷阱:
-
Runnable状态不等于“正在运行”,它包含“就绪”和“运行中”,但结合高 CPU,基本可断定此处是热点 - 如果看到
"VM Thread" nid=0x...,说明是 GC 线程在干活,得切到jstat -gcutil查 Full GC 是否频繁 - 堆栈里若大量出现
Math.sqrt、String.substring或正则Pattern.matcher,往往是计算密集型瓶颈,不是死循环
真正难的不是命令组合,而是理解堆栈里哪一行才是“起点”。有时候 loop 方法调了 service.process(),而 process 里有个 while,实际热点在 service 层的某行。得顺着堆栈往上翻,找到最深的那个 at 行——那里才是 CPU 真正卡住的地方。











