0。背景经常做后端服务开发的同学都或多或少遇到过CPU负载特别高的问题。尤其是周末或者半夜的时候,群里突然有人反映线上机器负载极高。不熟悉定位流程和思路的同学,登录服务器的时候可能手忙脚乱,定位流程来来回回。对此,不少同学整理出了相关的流程或方法。这类似于将大象放入冰箱需要多少步。传统方案一般是4步:topoderbywithP:1040//先按进程负载排序找到axLoad(pid)top-HpProcessPID:1073//找到相关负载线程PIDprintf"0x%x\n"ThreadPID:0x431//将线程PID转成16进制,为后面查找jstack日志做准备jstackprocessPID|vim+/hexadecimalthreadPID-//例如:jstack1040|vim+/0x431-但对于在线问题定位,分秒必争。以上4个步骤还是太繁琐费时了。能不能把它打包成一个Tool,出现问题的时候一键定位,秒级找到问题代码行?当然!工具链的成熟度不仅体现了一个开发者的运维能力,更体现了开发者的效率意识。淘宝的oldratlee将上述过程封装成一个工具:show-busy-java-threads.sh(点击直接下载,或参考文末链接下载),可以轻松在线定位此类问题。下面我举两个例子看看实际效果。快速安装使用:source<(curl-fsSLhttps://raw.githubusercontent.com/oldratlee/useful-scripts/master/test-cases/self-installer.sh)1、java正则表达式回源生成CPU100%importjava.util.ArrayList;导入java.util.List;导入java.util.regex.Matcher;导入java.util.regex.Pattern;公共类RegexLoad{publicstaticvoidmain(String[]args){String[]patternMatch={"([\\w\\s]+)+([+\\-/*])+([\\w\\s]+)","([\\w\\s]+)+([+\\-/*])+([\\w\\s]+)+([+\\-/*])+([\\w\\s]+)"};ListpatternList=newArrayList();patternList.add("AvgVolumeUnits产品A+VolumeUnits产品A");patternList.add("AvgVolumeUnits/VolumeUnitsproductA");patternList.add("Avg零售商手头/VolumeUnitsPlan/StoreCount");patternList.add("AvgHandVolumesUnitsPlanStoreCount");patternList.add("1-平均商户成交量Units");patternList.add("TotalretailershipmentCount");for(Strings:patternList){for(inti=0;ishow-busy-java-threads.sh-c<要显示的线程堆栈数>-p<指定的Java进程>#-F选项:执行jstack命令时加入-F选项(强制jstack)。一般情况下,不用show-busy-java-threads.sh-p<指定Java进程>-Fshow-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来执行,可以解决:sudoshow-busy-java-threads.sh例子:work@dev_zz_Master10.48.186.3223:45:50~/demo>bashshow-busy-java-threads.sh[1]繁忙(96.2%)线程(8577/0x2181)用户(工作)下的java进程(8576)堆栈:“main”prio=10tid=0x00007f0c64006800nid=0x2181runnable[0x00007f0c6a64a000]java.lang.Thread.State:RUNNABLEatjava.util.regex.Pattern$GroupHead.match(Pattern.java:4168)atjava.util.regex.Pattern$Loop.match(Pattern.java:4295)...在java.util.regex.Matcher.match(Matcher.java:1127)在java.util.regex.Matcher.matches(Matcher.java:502)在RegexLoad.main(RegexLoad.java:27)[2]用户(工作)下的java进程(8576)的繁忙(1.5%)线程(8591/0x218f)堆栈:“C2CompilerThread1”守护进程prio=10tid=0x00007f0c64095800nid=0x218f等待条件[0x0000000000000000000]java.ThreadRUNNABLE[3]繁忙(0.8%)线程(8590/0x218e)java进程堆栈(8576)在用户(工作)下:“C2CompilerThread0”守护进程prio=10tid=0x00007f0c64093000nid=0x218e等待条件[0x0000000000000000]java.lang.Thread.State:RUNNABLE[4]Busy(0.2%)ackstx(8591)0ofjavaprocess(8576)underuser(work):"VMPeriodicTaskThread"prio=10tid=0x00007f0c640a2800nid=0x2191waitingoncondition[5]Busy(0.1%)thread(25159/0x6247)stackofjavaprocess(25137)underuser(work):"VMPeriodicTaskThread"prio=10tid=0x00007f13340b4000nid=0x6247waitingonconditionwork@dev_zz_Master10.48.186.3223:46:04~/demo>可以看到,可以直接定位异常代码一键OK,是不是很方便?2.线程死锁,程序挂起importjava.util.*;publicclassSimpleDeadLockextendsThread{publicstaticObjectl1=newObject();publicstaticObjectl2=newObject();私有整数索引;publicstaticvoidmain(String[]a){Threadt1=newThread1();线程t2=newThread2();t1.开始();t2.开始();}私人的staticclassThread1extendsThread{publicvoidrun(){synchronized(l1){System.out.println("Thread1:Holdinglock1...");尝试{Thread.sleep(10);}catch(InterruptedExceptione){}System.out.println("Thread1:Waitingforlock2...");synchronized(l2){System.out.println("Thread2:Holdinglock1&2...");}}}}privatestaticclassThread2extendsThread{publicvoidrun(){synchronized(l2){System.out.println("Thread2:Holdinglock2...");尝试{Thread.sleep(10);}catch(InterruptedExceptione){}System.out.println("Thread2:Waitingforlock1...");synchronized(l1){System.out.println("线程2:持有lock2&1...");}}}}}执行后的效果:如何使用工具定位:一键定位:可以清楚的看到线程已经锁定了彼此等待对方的资源,导致indeadlock,然后直接定位到代码行和具体原因。通过上面两个例子,我想各位同学应该对这个工具有了更深入的了解,能解决什么问题。如果遇到CPU100%的问题,大家可以不慌了。不过更多的还是要靠大家自己去实践,毕竟实践出真知~3.免费实用的脚本工具大礼包除了里面提到的show-busy-java-threads.sh文中,oldratlee还集成了很多常见的开发运维过程中涉及到的脚本工具,我觉得特别好用,简单罗列如下:(1)show-duplicate-java-classes偶尔会遇到莫名其妙类异常在本地开发测试是正常的,但是上线之后,千辛万苦查出来的原因是Jar冲突!这个工具可以找出JavaLib(Java库,即Jar文件)或Class目录(类目录)中的重复类。Java开发中比较头疼的一个问题就是Jar冲突(就是多个A版本的Jar),或者重复的类。会出现NoSuchMethod等问题,但到时候可能不是问题。找出有重复类的Jar,这样可以防患于未然。#查找当前目录下所有Jars中重复的classesshow-duplicate-java-classes#查找多个指定目录下所有Jars中重复的classesshow-duplicate-java-classespath/to/lib_dir1/path/to/lib_dir2#查找在多个指定的类目录中复制类。Class目录由-c选项指定show-duplicate-java-classes-cpath/to/class_dir1-c/path/to/class_dir2#查找指定Class目录的Jarshow-duplicate-java和该目录下的所有Jar指定的目录。-classespath/to/lib_dir1/path/to/lib_dir2-cpath/to/class_dir1-cpath/to/class_dir2例如:#在war模块目录下执行生成war文件$mvninstall...#解压war文件,war文件包含应用程序依赖的Jar文件$unziptarget/*.war-dtarget/war...#Checkforduplicateclasses$show-duplicate-java-classes-ctarget/war/WEB-INF/classestarget/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/directoryfind-in-jarslog4j\\.xmlfind-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)housemdpid[java_home]很早的时候,我们使用BTrace来解决问题。除了厉害之外,他还折腾过几次,挂断过联机系统。2012年,淘宝巨石写了HouseMD,整合了几个常用的Btrace脚本,形成一个独立风格的应用。其核心代码使用Scala,HouseMD是一个基于字节码技术的诊断工具。因此,除了Java之外,任何最终以字节码形式运行在JVM上的语言,HouseMD都支持对它们进行诊断,例如Clojure(感谢@Killme2008提供了使用介绍)、scala、Groovy、JRuby、Jython、kotlin等。使用housemd跟踪java程序的运行时间,支持的操作有:查看加载类跟踪方法查看环境变量查看对象属性值详情请参考:https://github.com/CSUG/House...(4)jvmpid执行jvm调试工具,包括查看java栈、堆、线程、gc等状态,支持的功能有:========线程相关=======1:查看CPU占用率最高的线程情况2:打印所有线程3:打印线程数4:按线程状态统计线程数========GC相关=======5:垃圾收集统计(包括原因)可以指定间隔时间和执行次数,默认1秒,10次6:显示堆中每一代的空间,可以指定间隔时间和执行次数,默认1秒,5乘以7:垃圾回收统计。可以指定间隔时间和执行次数,默认1秒,10次8:打印perm区的内存状态*会导致程序暂停响应*9:查看directbuffer状态========堆对象相关=======10:dumpheaptoafile*会导致程序暂停响应*默认保存到`pwd`/dump.bin,可以指定其他路径11:触发完整的gc。*它会导致程序暂停响应*12:打印jvm堆统计信息*它会导致程序暂停响应*13:打印jvm堆中的top20对象。*会导致程序暂停响应*参数:1:按实例数排序,2:按内存占用排序,默认为114:触发fullgc后,打印jvm堆中的top20个对象。*会导致程序暂停响应*参数:1:按实例数排序,2:按内存占用排序,默认为115:输出perm中类加载器产生的所有对象。可以指定间隔时间和执行次数========其他=======16:打印finalzer队列状态17:显示classloader统计信息18:显示jit编译统计信息19:死锁检测20:等待Xseconds,默认为1q:exit进入jvm工具后,可以输入序列号执行相应的命令。可以一次执行多条命令,用分号“;”隔开,如:1;3;4;5;6每条命令可以有参数,用分号“:”隔开,命令之间用逗号隔开相同命令的参数,如:Entercommandqueue:1;5:1000,100;10:/data1/output.bin(5)greys[@IP:PORT]PS:目前Grays只支持Java6+onLinux/Unix/Mac,Windows暂时不支持。Grays是JVM进程执行过程中的异常诊断工具,可以在不中断程序执行的情况下轻松完成故障排除。和HouseMD一样,Greys-Anatomy将这部美剧命名为同名的《实习医生格蕾》,以此向前辈致敬。写代码的时候参考了BTrace和HouseMD两位前辈的思路。使用greys在运行时跟踪java程序(不传参,需要先greys-Cpid,再greys)。支持的操作有:查看加载类、方法信息查看JVM当前基本信息方法执行监控(调用量、失败率、响应时间等)方法执行数据观察、记录和回放(参数、返回结果、异常信息等))方法调用trackingrendering详情请参考:https://github.com/oldmanpush...(6)sjksjk--commandssjk--help使用sjk诊断,检查性能和优化Java工具ttop:监控指定jvm进程的每个线程的cpu使用情况jps:增强版hh:jmap-histo增强版gc:实时上报垃圾回收信息更多信息参考:https//github.com/aragozin/j。..参考:[1]oldratlee/useful-scriptshttps://github.com/oldratlee/...[2]awesome-scriptshttps://github.com/superhj198...[3]JDK自带工具问题排查场景示例http://bit.ly/2xtukcb[4]Java调优经验谈http://bit.ly/2xCIj2L[5]jvm排查工具箱jvm-toolshttps://segmentfault.com/a/11...[6]alibaba/arthashttps://github.com/alibaba/ar...