共计 8600 个字符,预计需要花费 22 分钟才能阅读完成。
0、背景
常常做后端服务开发的同学,或多或少都遇到过 CPU 负载特地高的问题。尤其是在周末或大半夜,忽然群里有人反馈线上机器负载特地高,不相熟定位流程和思路的同学可能登上服务器一通慌手慌脚,定位过程百转千回。
对此,也有不少同学已经整顿过相干流程或方法论,相似把大象放进冰箱要几步,传统的计划个别是 4 步:
- top oder by with P:1040 // 首先按过程负载排序找到 axLoad(pid)
- top -Hp 过程 PID:1073 // 找到相干负载 线程 PID
- printf“0x%x\n”线程 PID:0x431 // 将线程 PID 转换为 16 进制,为前面查找 jstack 日志做筹备
- jstack 过程 PID | vim +/ 十六进制线程 PID – // 例如:jstack 1040|vim +/0x431 –
然而对于线上问题定位来说,争分夺秒,下面的 4 步还是太繁琐耗时了,有没有可能封装成为一个工具,在有问题的时候一键定位,秒级找到有问题的代码行呢?
当然能够!工具链的成熟与否不仅体现了一个开发者的运维能力,也体现了开发者的效率意识。淘宝的 oldratlee 同学就将下面的流程封装为了一个工具:show-busy-java-threads.sh,能够很不便的定位线上的这类问题,上面我会举两个例子来看理论的成果。
疾速装置应用:
`source <(curl -fsSL https://raw.githubusercontent.com/oldratlee/useful-scripts/master/test-cases/self-installer.sh)`
1、java 正则表达式回溯造成 CPU 100%
`import java.util.ArrayList;`
`import java.util.List;`
`import java.util.regex.Matcher;`
`import java.util.regex.Pattern;`
`public class RegexLoad {`
`public static void main(String[] args) {`
`String[] patternMatch = {"([\\w\\s]+)+([+\\-/*])+([\\w\\s]+)",`
`"([\\w\\s]+)+([+\\-/*])+([\\w\\s]+)+([+\\-/*])+([\\w\\s]+)"};`
`List<String> patternList = new ArrayList<String>();`
`patternList.add("Avg Volume Units product A + Volume Units product A");`
`patternList.add("Avg Volume Units / Volume Units product A");`
`patternList.add("Avg retailer On Hand / Volume Units Plan / Store Count");`
`patternList.add("Avg Hand Volume Units Plan Store Count");`
`patternList.add("1 - Avg merchant Volume Units");`
`patternList.add("Total retailer shipment Count");`
`for (String s :patternList){`
`for(int i=0;i<patternMatch.length;i++){`
`Pattern pattern = Pattern.compile(patternMatch[i]);`
`Matcher matcher = pattern.matcher(s);`
`System.out.println(s);`
`if (matcher.matches()) {`
`System.out.println("Passed");`
`}else`
`System.out.println("Failed;");`
`}`
`}`
`}`
`}`
编译、运行上述代码之后,咱们就能察看到服务器多了一个 100% CPU 的 java 过程:
怎么应用呢?
`show-busy-java-threads.sh`
`# 从 所有的 Java 过程中找出最耗费 CPU 的线程(缺省 5 个),打印出其线程栈。`
`show-busy-java-threads.sh -c < 要显示的线程栈数 >`
`show-busy-java-threads.sh -c < 要显示的线程栈数 > -p < 指定的 Java Process>`
`# - F 选项:执行 jstack 命令时加上 - F 选项(强制 jstack),个别状况不须要应用 `
`show-busy-java-threads.sh -p < 指定的 Java Process> -F`
`show-busy-java-threads.sh -s < 指定 jstack 命令的全门路 >`
`# 对于 sudo 形式的运行,JAVA_HOME 环境变量不能传递给 root,`
`# 而 root 用户往往没有配置 JAVA_HOME 且不不便配置,`
`# 显式指定 jstack 命令的门路就反而显得更不便了 `
`show-busy-java-threads.sh -a < 输入记录到的文件 >`
`show-busy-java-threads.sh -t < 反复执行的次数 > -i < 反复执行的距离秒数 >`
`# 缺省执行一次;执行距离缺省是 3 秒 `
`##############################`
`# 留神:`
`##############################`
`# 如果 Java 过程的用户 与 执行脚本的以后用户 不同,则 jstack 不了这个 Java 过程。`
`# 为了能切换到 Java 过程的用户,须要加 sudo 来执行,即能够解决:`
`sudo show-busy-java-threads.sh`
示例:
`work@dev_zz_Master 10.48.186.32 23:45:50 ~/demo >`
`bash show-busy-java-threads.sh`
`[1] Busy(96.2%) thread(8577/0x2181) stack of java process(8576) under user(work):`
`"main" prio=10 tid=0x00007f0c64006800 nid=0x2181 runnable [0x00007f0c6a64a000]`
`java.lang.Thread.State: RUNNABLE`
`at java.util.regex.Pattern$GroupHead.match(Pattern.java:4168)`
`at java.util.regex.Pattern$Loop.match(Pattern.java:4295)`
`...`
`at java.util.regex.Matcher.match(Matcher.java:1127)`
`at java.util.regex.Matcher.matches(Matcher.java:502)`
`at RegexLoad.main(RegexLoad.java:27)`
`[2] Busy(1.5%) thread(8591/0x218f) stack of java process(8576) under user(work):`
`"C2 CompilerThread1" daemon prio=10 tid=0x00007f0c64095800 nid=0x218f waiting on condition [0x0000000000000000]`
`java.lang.Thread.State: RUNNABLE`
`[3] Busy(0.8%) thread(8590/0x218e) stack of java process(8576) under user(work):`
`"C2 CompilerThread0" daemon prio=10 tid=0x00007f0c64093000 nid=0x218e waiting on condition [0x0000000000000000]`
`java.lang.Thread.State: RUNNABLE`
`[4] Busy(0.2%) thread(8593/0x2191) stack of java process(8576) under user(work):`
`"VM Periodic Task Thread" prio=10 tid=0x00007f0c640a2800 nid=0x2191 waiting on condition`
`[5] Busy(0.1%) thread(25159/0x6247) stack of java process(25137) under user(work):`
`"VM Periodic Task Thread" prio=10 tid=0x00007f13340b4000 nid=0x6247 waiting on condition`
`work@dev_zz_Master 10.48.186.32 23:46:04 ~/demo >`
能够看到,一键间接定位异样代码行,是不是很不便?
2、线程死锁,程序 hang 住
`import java.util.*;`
`public class SimpleDeadLock extends Thread {`
`public static Object l1 = new Object();`
`public static Object l2 = new Object();`
`private int index;`
`public static void main(String[] a) {`
`Thread t1 = new Thread1();`
`Thread t2 = new Thread2();`
`t1.start();`
`t2.start();`
`}`
`private static class Thread1 extends Thread {`
`public void run() {`
`synchronized (l1) {`
`System.out.println("Thread 1: Holding lock 1...");`
`try {Thread.sleep(10); }`
`catch (InterruptedException e) {}`
`System.out.println("Thread 1: Waiting for lock 2...");`
`synchronized (l2) {`
`System.out.println("Thread 2: Holding lock 1 & 2...");`
`}`
`}`
`}`
`}`
`private static class Thread2 extends Thread {`
`public void run() {`
`synchronized (l2) {`
`System.out.println("Thread 2: Holding lock 2...");`
`try {Thread.sleep(10); }`
`catch (InterruptedException e) {}`
`System.out.println("Thread 2: Waiting for lock 1...");`
`synchronized (l1) {`
`System.out.println("Thread 2: Holding lock 2 & 1...");`
`}`
`}`
`}`
`}`
`}`
执行之后的成果:
如何用工具定位:
一键定位:能够清晰的看到线程相互锁住了对方期待的资源,导致死锁,间接定位到代码行和具体起因。
通过下面两个例子,我想各位同学应该对这个工具和工具能解决什么问题有了比拟粗浅的理解了,遇到 CPU 100% 问题能够从此不再慌乱。然而更多的还是依赖大家本人去实际,毕竟实际出真知嘛~
3、收费实用的脚本工具大礼包
除了注释提到的 show-busy-java-threads.sh,oldratlee 同学还整合和不少常见的开发、运维过程中波及到的脚本工具,感觉特地有用的我简略列下:
(1) show-duplicate-java-classes
偶然会遇到本地开发、测试都失常,上线后却莫名其妙的 class 异样,历经含辛茹苦找到的起因居然是 Jar 抵触!这个工具就能够找出 Java Lib(Java 库,即 Jar 文件)或 Class 目录(类目录)中的反复类。
Java 开发的一个麻烦的问题是 Jar 抵触(即多个版本的 Jar),或者说反复类。会出 NoSuchMethod 等的问题,还不见得过后出问题。找出有反复类的 Jar,能够防患未然。
`# 查找当前目录下所有 Jar 中的反复类 `
`show-duplicate-java-classes`
`# 查找多个指定目录下所有 Jar 中的反复类 `
`show-duplicate-java-classes path/to/lib_dir1 /path/to/lib_dir2`
`# 查找多个指定 Class 目录下的反复类。Class 目录 通过 -c 选项指定 `
`show-duplicate-java-classes -c path/to/class_dir1 -c /path/to/class_dir2`
`# 查找指定 Class 目录和指定目录下所有 Jar 中的反复类的 Jar`
`show-duplicate-java-classes path/to/lib_dir1 /path/to/lib_dir2 -c path/to/class_dir1 -c path/to/class_dir2`
例如:
`# 在 war 模块目录下执行,生成 war 文件 `
`$ mvn install`
`...`
`# 解压 war 文件,war 文件中蕴含了利用的依赖的 Jar 文件 `
`$ unzip target/*.war -d target/war`
`...`
`# 查看反复类 `
`$ show-duplicate-java-classes -c target/war/WEB-INF/classes target/war/WEB-INF/lib`
`...`
(2) find-in-jars
在当前目录下所有 jar 文件里,查找类或资源文件。
用法:留神,前面 Pattern 是 grep 的 扩大正则表达式。
`find-in-jars 'log4j\.properties'`
`find-in-jars 'log4j\.xml$' -d /path/to/find/directory`
`find-in-jars log4j\\.xml`
`find-in-jars 'log4j\.properties|log4j\.xml'`
示例:
`$ ./find-in-jars 'Service.class$'`
`./WEB-INF/libs/spring-2.5.6.SEC03.jar!org/springframework/stereotype/Service.class`
`./rpc-benchmark-0.0.1-SNAPSHOT.jar!com/taobao/rpc/benchmark/service/HelloService.class`
(3) housemd pid [java\_home]
很早的时候,咱们应用 BTrace 排查问题,在感叹 BTrace 的弱小之余,也曾好几次将线上零碎折腾挂掉。2012 年淘宝的聚石写了 HouseMD,将罕用的几个 Btrace 脚本整合在一起造成一个独立格调的利用,其外围代码用的是 Scala,HouseMD 是基于字节码技术的诊断工具, 因而除了 Java 以外, 任何最终以字节码模式运行于 JVM 之上的语言, HouseMD 都反对对它们进行诊断, 如 Clojure(感激 @Killme2008 提供了它的应用入门), scala, Groovy, JRuby, Jython, kotlin 等.
应用 housemd 对 java 程序进行运行时跟踪,反对的操作有:
- 查看加载类
- 跟踪办法
- 查看环境变量
- 查看对象属性值
- 详细信息请参考: https://github.com/CSUG/House…
(4) jvm pid
执行 jvm debug 工具,蕴含对 java 栈、堆、线程、gc 等状态的查看,反对的性能有:
`======== 线程相干 =======`
`1 : 查看占用 cpu 最高的线程状况 `
`2 : 打印所有线程 `
`3 : 打印线程数 `
`4 : 按线程状态统计线程数 `
`========GC 相干 =======`
`5 : 垃圾收集统计(蕴含起因)能够指定间隔时间及执行次数,默认 1 秒, 10 次 `
`6 : 显示堆中各代的空间能够指定间隔时间及执行次数,默认 1 秒,5 次 `
`7 : 垃圾收集统计。能够指定间隔时间及执行次数,默认 1 秒, 10 次 `
`8 : 打印 perm 区内存状况 * 会使程序暂停响应 *`
`9 : 查看 directbuffer 状况 `
`======== 堆对象相干 =======`
``10 : dump heap 到文件 * 会使程序暂停响应 * 默认保留到 `pwd`/dump.bin, 可指定其它门路 ``
`11 : 触发 full gc。* 会使程序暂停响应 *`
`12 : 打印 jvm heap 统计 * 会使程序暂停响应 *`
`13 : 打印 jvm heap 中 top20 的对象。* 会使程序暂停响应 * 参数:1: 按实例数量排序,2: 按内存占用排序,默认为 1`
`14 : 触发 full gc 后打印 jvm heap 中 top20 的对象。* 会使程序暂停响应 * 参数:1: 按实例数量排序,2: 按内存占用排序,默认为 1`
`15 : 输入所有类装载器在 perm 里产生的对象。能够指定间隔时间及执行次数 `
`======== 其它 =======`
`16 : 打印 finalzer 队列状况 `
`17 : 显示 classloader 统计 `
`18 : 显示 jit 编译统计 `
`19 : 死锁检测 `
`20 : 期待 X 秒,默认为 1`
`q : exit`
进入 jvm 工具后能够输出序号执行对应命令
能够一次执行多个命令,用分号 ”;” 分隔,如:1;3;4;5;6
每个命令能够带参数,用冒号 ”:” 分隔,同一命令的参数之间用逗号分隔,如:
Enter command queue:1;5:1000,100;10:/data1/output.bin
(5) greys <PID>[@IP:PORT]
PS:目前 Greys 仅反对 Linux/Unix/Mac 上的 Java6+,Windows 临时无奈反对
Greys 是一个 JVM 过程执行过程中的异样诊断工具,能够在不中断程序执行的状况下轻松实现问题排查工作。和 HouseMD 一样,Greys-Anatomy 取名同名美剧“实习医生格蕾”,目标是向前辈致敬。代码编写的时候参考了 BTrace 和 HouseMD 两个前辈的思路。
应用 greys 对 java 程序进行运行时跟踪 (不传参数,须要先 greys -C pid, 再 greys)。反对的操作有:
- 查看加载类,办法信息
- 查看 JVM 以后根底信息
- 办法执行监控(调用量,失败率,响应工夫等)
- 办法执行数据观测、记录与回放(参数,返回后果,异样信息等)
- 办法调用追踪渲染
- 详细信息请参考: https://github.com/oldmanpush…
(6) sjk <cmd> <arguments> sjk –commands sjk –help <cmd>
应用 sjk 对 Java 诊断、性能排查、优化工具
- ttop: 监控指定 jvm 过程的各个线程的 cpu 应用状况
- jps: 强化版
- hh: jmap -histo 强化版
- gc: 实时报告垃圾回收信息
- 更多信息请参考: github.com/aragozin/jvm-tools
Refer:
- oldratlee/useful-scripts
github.com/oldratlee/useful-scripts - awesome-scripts
github.com/superhj1987/awesome-scripts - JDK 自带工具之问题排查场景示例
bit.ly/2xtukcb - Java 调优经验谈
bit.ly/2xCIj2L - jvm 排查工具箱 jvm-tools
segmentfault.com/a/1190000012658814 -
alibaba/arthas
github.com/alibaba/arthas/blob/7f236219ddbd040764dd821cbcbd44899dd57c90/README.md源于:my.oschina.net/leejun2005/blog/1524687