上一篇咱们讲了 jstack dump 线程快照,其中最关键的一步就是用「grep+awk+sort+uniq -c」这串 指令,快速统计线程状态分布,避开了手动翻几百行快照的麻烦。
其实它本质就是 4 个 "小工具" 流水线干活,今天咱们就来讲一讲~
一、这串指令到底在干一件什么事?
咱们先回到上一篇的场景:导出的 dump 文件有几百行,里面混着各种线程信息,我们的核心需求是「快速统计每种线程状态有多少个」。
这串指令:
bash
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c
本质就是 "筛选→提取→排序→计数" 的流水线作业 ,咱们用 "整理文件" 类比,一下子就能懂:
- 第一步精准过滤(grep):从一堆文件(dump17)里,只挑出 "写了线程状态" 的纸条(比如只留 "java.lang.Thread.State: WAITING" 这类行);
- 第二步提取关键字(awk):把挑出来的纸条,只保留 "线程状态" 这几个字(比如把 "java.lang.Thread.State: WAITING (on object monitor)" 简化成 "WAITING (onobjectmonitor)");
- 第三步排序(sort):把相同状态的纸条排在一起(比如所有 "WAITING" 纸条放一堆,"RUNNABLE" 纸条放一堆);
- 第四步去重并计数(uniq -c):数每堆纸条有多少张,在纸条前面写上数字(比如 "305 WAITING (onobjectmonitor)")。
整个过程,就像你整理办公室的文件,先挑有用的、再提炼关键信息、再分类、最后统计数量。
二、4 个指令
咱们逐个拆解这 4 个指令,只讲和线程快照统计相关的用法。
第一步:grep ------ 「文件里找东西的神器」(筛选)
grep 就像 "找东西的放大镜",核心作用是:从指定文件里,筛选出 "包含某个关键词" 的所有行,没用的行直接过滤掉。
线程快照里的核心用法
bash
grep java.lang.Thread.State dump17
说明:
-
关键词:
java.lang.Thread.State------ 这是 Java 线程状态的固定前缀(dump 文件里,所有线程状态行都会包含这个字符串); -
目标文件:
dump17------ 我们上一篇导出的线程快照文件; -
执行结果:会把 dump17 里,所有包含 "线程状态" 的行都输出来,示例如下(只截取 2 行)
java.lang.Thread.State: RUNNABLE java.lang.Thread.State: WAITING (on object monitor)
grep 高频参数
不用记太多,掌握这 4 个,能解决 90% 的筛选需求
| 参数 | 解释 | 线程快照实操例子 | 执行效果 |
|---|---|---|---|
| -n | 显示筛选结果的「行号」 | grep -n java.lang.Thread.State dump17 | 输出包含线程状态的行,同时显示这行在 dump17 里的行号(方便定位) |
| -i | 忽略大小写 | grep -i "waiting" dump17 | 同时筛选 "WAITING""waiting""Waiting"(dump 里一般是大写,但日志里可能有小写) |
| -v | 反向筛选(排除关键词) | grep -v "RUNNABLE" dump17 | 筛选出 "不包含 RUNNABLE" 的线程状态行(只看阻塞 / 等待的线程) |
| -c | 只统计「筛选到的行数」 | grep -c java.lang.Thread.State dump17 | 直接输出 "dump17 里有多少行包含线程状态"(比如输出 398,代表有 398 个线程) |
✅ 小技巧:线上排查时,先用「grep -c 关键词 文件名」快速统计数量,再用「grep -n 关键词 文件名」定位具体行,效率翻倍。
第二步:awk ------ 「提取关键信息的小刀」
awk 就像 "一把小刀",核心作用是:把 grep 筛选出来的行,"切" 成多列,只提取我们需要的列。
为什么需要它?------ 上一步 grep 输出的行,包含多余的前缀(java.lang.Thread.State:),我们只需要 "RUNNABLE""WAITING (on object monitor)" 这部分,这时候就用 awk 来 "切"。
线程快照里的核心用法
bash
awk '{print $2$3$4$5}'
说明
-
awk 默认会把「一行内容」按「空格」分成多列,列数从1开始计数(1 是第一列,$2 是第二列,以此类推);
-
我们先看 grep 输出的行(awk 的输入就是 grep 的输出):
java.lang.Thread.State: RUNNABLE # 按空格拆分,$1=java.lang.Thread.State:,$2=RUNNABLE java.lang.Thread.State: WAITING (on object monitor) # $1=前缀,$2=WAITING,$3=(on,$4=object,$5=monitor) -
指令解读:
print $2$3$4$5- $2:提取第二列(比如 RUNNABLE、WAITING);
- 34$5:提取第三、四、五列(对应 "(onobjectmonitor)",去掉空格);
- 连起来写
$2$3$4$5:是为了把 "WAITING (on object monitor)" 变成 "WAITING (onobjectmonitor)"(去掉空格,方便后续排序和计数);
-
执行结果(结合上一步 grep):
bashgrep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'输出就是线程状态关键词:
RUNNABLE WAITING(onobjectmonitor) TIMED_WAITING(sleeping)
- 不用记复杂的 awk 语法,只记「$ 数字」就是 "提取第 N 列";
- 为什么不用1?------ 1 是 "java.lang.Thread.State:",是我们不需要的前缀,所以从 $2 开始;
- 为什么连写2345?------ 因为不同状态的列数不一样(RUNNABLE 只有2,WAITING有2-$5),连写能保证 "不管多少列,都能完整提取状态",不会遗漏。
第三步:sort ------ 「给关键信息排序的工具」(排序)
sort 就像 "整理卡片",核心作用是:把 awk 提取出来的 "线程状态关键词",按字母顺序排序,把相同的状态排在一起。
为什么需要它?------ 如果不排序,相同的状态会分散在各处(比如第一个是 WAITING,第二个是 RUNNABLE,第三个又是 WAITING),后续无法统计数量。
线程快照里的核心用法(必记)
bash
sort
说明(结合前面两步)
bash
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort
执行结果
会把所有线程状态关键词,按字母顺序排好,相同的状态聚在一起:
RUNNABLE
RUNNABLE
TIMED_WAITING(onobjectmonitor)
TIMED_WAITING(sleeping)
WAITING(onobjectmonitor)
WAITING(onobjectmonitor)
小补充
sort 默认是「升序排序」(A-Z),如果想降序,加
-r参数(比如sort -r),但线程快照统计里,升序就足够用了。
第四步:uniq -c ------ 「统计数量的计数器」(计数)
uniq 就像 "数卡片的计数器",核心作用是:把 sort 排好序的 "相同状态" 去重,同时统计每种状态出现的次数。
- 不加
-c:只去重(比如把多个 RUNNABLE 变成 1 个 RUNNABLE); - 加
-c:去重 + 计数(这是我们需要的,统计每种状态有多少个线程)。
线程快照里的核心用法
bash
uniq -c
拆解说明
bash
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c
执行结果
39 RUNNABLE
21 TIMED_WAITING(onobjectmonitor)
6 TIMED_WAITING(parking)
51 TIMED_WAITING(sleeping)
305 WAITING(onobjectmonitor)
3 WAITING(parking)
结果解读
- 前面的数字:每种线程状态的数量(比如 39 代表有 39 个 RUNNABLE 线程,305 代表有 305 个 WAITING 线程);
- 后面的关键词:线程状态(和 awk 提取的一致)。
✅ 到这里,我们就完成了 "从几百行快照里,1 行指令统计出所有线程状态数量" 的操作 ------ 不用手动翻,不用手动数,精准又高效。
三、这串指令能改成哪些常用场景?
学会了拆解,我们就可以根据自己的需求,改造这串指令,以下 3 个场景是线上排查高频,直接复制可用:
场景 1:统计 dump 文件里,阻塞(BLOCKED)线程的数量
bash
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c | grep BLOCKED
在原来的统计结果里,再用 grep 筛选出 "包含 BLOCKED" 的行,直接得到 "BLOCKED 线程有多少个"(比如输出 "5 BLOCKED (onobjectmonitor)",代表有 5 个线程阻塞)。
场景 2:统计日志里,ERROR 出现的次数(通用场景)
bash
grep -i "error" app.log | awk '{print $1}' | sort | uniq -c
- grep -i "error" app.log:筛选出日志里所有包含 error 的行(忽略大小写);
- awk '{print $1}':提取第一列(一般是日期,比如 2024-05-01);
- sort | uniq -c:统计每天出现多少个 ERROR,方便定位 "哪一天出了问题"。
场景 3:忽略大小写,统计 dump 里所有等待状态的线程总数
bash
grep -i "waiting" dump17 | grep -c java.lang.Thread.State
- grep -i "waiting" dump17:筛选出所有包含 "等待" 关键词的行;
- grep -c java.lang.Thread.State:只统计其中 "属于线程状态" 的行,得到等待线程的总数(不用排序,直接计数)。
四、必避的 3 个坑
- 坑 1:指令里少写「|」(管道符) → 管道符是 "流水线的传送带",把前一个指令的输出,传给后一个指令,少写一个,指令就会报错(比如 grep 和 awk 之间必须加 |);
- 坑 2:awk 的数字写错 → 比如把2 写成1,会提取到"java.lang.Thread.State:"这个前缀,导致后续统计出错,记住:线程状态从2 开始提取;
- 坑 3:先 uniq 再 sort → 必须先 sort(把相同状态排在一起),再 uniq -c,否则 uniq 无法正确去重计数(比如先 uniq,相同状态分散在各处,会被当成不同的状态,统计结果不准)。
五、这串指令的完整拆解话术
"你用 grep+awk+sort+uniq -c 统计线程状态,每一步都在做什么?"
"这串指令是一个'筛选→提取→排序→计数'的流水线,每一步的作用如下:
- grep:从 dump 文件里,筛选出所有包含线程状态(java.lang.Thread.State)的行,过滤掉无用信息;
- awk:把筛选出来的行,按空格拆分,提取出线程状态关键词(从2开始,连写2-$5,避免遗漏状态描述);
- sort:把提取到的线程状态关键词按字母排序,把相同的状态聚在一起,为后续计数做准备;
- uniq -c:对排序后的状态进行去重,同时统计每种状态出现的次数,最终得到每种线程状态的数量分布,快速定位异常。"
六、小结
今天咱们了解了「grep+awk+sort+uniq -c」这串 "神指令",就是记住 4 个步骤:筛选(grep)→ 提取(awk)→ 排序(sort)→ 计数(uniq -c)。
重点不在于记指令本身,而在于理解 "流水线" 的逻辑 ------ 不管是线程快照、日志分析,只要是 "从大量文本里找关键信息、统计数量",都可以用这个逻辑改造指令。
学会这串指令,你线上排查的效率会翻倍,不用再手动翻几百、几千行文本,1 行指令就能搞定统计,面试时也能轻松拆解,加分不少。