Arthas 是基于 Greys 进行二次开发的全新在线诊断工具,利用Java6的Instrumentation特性,动态增强你所指定的类,获取你想要到的信息, 采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,让你在定位、分析诊断问题时看每一个操作都看起来是那么的 666
阿里已开源 https://github.com/alibaba/ar…
下载压缩包,上传到需要被诊断的机器, 解压缩
当前系统的实时数据面板
查看当前 JVM 的线程堆栈信息
查看当前 JVM 的信息
查看JVM已加载的类信息
查看已加载类的方法信息
反编译指定已加载类的源码
查看classloader的继承树,urls,类加载信息,使用classloader去getResource
方法执行监控
方法执行数据观测
方法内部调用路径,并输出方法路径上的每个节点上耗时
输出当前方法被调用的调用路径
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类
退出当前 Arthas 客户端,其他 Arthas 客户端不受影响
关闭 Arthas 服务端,所有 Arthas 客户端全部退出
ID: Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应
NAME: 线程名
GROUP: 线程组名
PRIORITY: 线程优先级, 1~10之间的数字,越大表示优先级越高
STATE: 线程的状态
CPU%: 线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。
TIME: 线程运行总时间
INTERRUPTE: 线程当前的中断位状态
DAEMON: 是否是daemon线程
线程id
指定最忙的前N个线程并打印堆栈
找出当前阻塞其他线程的线程
指定cpu占比统计的采样间隔,单位为毫秒
PS: 这里的cpu统计的是,一段采样间隔内,当前JVM里各个线程所占用的cpu时间占总cpu时间的百分比。其计算方法为: 首先进行一次采样,获得所有线程的cpu的使用时间(调用的是java.lang.management.ThreadMXBean#getThreadCpuTime这个接口),然后睡眠一段时间,默认100ms,可以通过-i参数指定,然后再采样一次,最后得出这段时间内各个线程消耗的cpu时间情况,最后算出百分比。注意: 这个统计也会产生一定的开销(JDK这个接口本身开销比较大),因此会看到as的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如5000毫秒。
类名表达式匹配
输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。如果一个类被多个ClassLoader所加载,则会出现多次
开启正则表达式匹配,默认为通配符匹配
输出当前类的成员变量信息(需要配合参数-d一起使用)
指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出
PS: class-pattern支持全限定名,如com.test.AAA,也支持com/test/AAA这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦,
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关
类名表达式匹配
方法名表达式匹配
-d
展示每个方法的详细信息
-E
开启正则表达式匹配,默认为通配符匹配
PS:查看已加载类的方法信息, “Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到
monitor 命令是一个非实时返回命令,实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而被不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何 Arthas 的命令也不会引起任何原有业务逻辑的改变
类名表达式匹配
方法名表达式匹配
-c
统计周期,默认值为120秒
timestamp 时间戳
class java类
method 方法(构造方法、普通方法)
total 调用次数
success 成功次数
fail 失败次数
rt 平均rt
fail-rate 失败率
PS:方法执行监控, 对匹配 class-pattern/method-pattern的类、方法的调用进行监控。
方法内部调用路径,并输出方法路径上的每个节点上耗时, trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
trace 能方便的帮助你定位和发现因 RT 高而导致的性能问题缺陷,但其每次只能跟踪一级方法的调用链路
trace 在执行的过程中本身是会有一定的性能开销,在统计的报告中并未像 JProfiler 一样预先减去其自身的统计开销。所以这统计出来有些许的不准,渲染路径上调用的类、方法越多,性能偏差越大。但还是能让你看清一些事情的。
类名表达式匹配
方法名表达式匹配
条件表达式
命令执行次数
方法执行耗时
PS: 很多时候我们只想看到某个方法的rt大于某个时间之后的trace结果,例如trace *StringUtils isBlank ‘$cost>100’表示当执行时间超过100ms的时候,才会输出trace的结果。
输出当前方法被调用的调用路径, 很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
类名表达式匹配
方法名表达式匹配
条件表达式
执行次数限制
方法执行数据观测, 让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 groovy 表达式进行对应变量的查看。
类名表达式匹配
方法名表达式匹配
观察表达式
条件表达式
-b
在方法调用之前观察(默认关闭)
-e
在方法异常之后观察(默认关闭)
-s
在方法返回之后观察(默认关闭)
-f
在方法结束之后(正常返回和异常返回)观察 (默认开启)
-x
指定输出结果的属性遍历深度,默认为0
PS:这里重点要说明的是观察表达式,观察表达式的构成主要由 groovy 表达式组成,只要是一个合法的 groovy 表达式,都能被正常支持。
观察的维度也比较多,主要体现在参数 advice 的数据结构上。Advice 参数最主要是封装了通知节点的所有信息。
来源: https://segmentfault.com/a/1190000014618329
———————————
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/w57685321/article/details/84307516
Arthas 是阿里巴巴最近才开源出来的一款 Java 诊断利器,它主要是针对线上环境,能够帮助我们更好的定位问题。
Case:https://github.com/alibaba/arthas/issues?q=label%3Auser-case
官方文档: https://alibaba.github.io/arthas
官方文档还是比较详细的,这里就挑几个觉得好用的来实操一下
下载好后,目录是这样的,win运行bat脚本即可, as.bat +
pid可以通过jps命令找出来
package com.bfxy.springboot; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class Demo { private static AtomicInteger count = new AtomicInteger(0); public static void increment() { count.incrementAndGet(); } public static int value() { return count.get(); } public static void main(String[] args) throws InterruptedException { while (true) { increment(); System.out.println("counter: " + value()); TimeUnit.SECONDS.sleep(1); } } }
然后写了个demo,attach上去
就到了启动界面
方法执行数据观测
让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
输入watch命令,监视Demo类中的value方法的返回值
发现跟这个是对应的,监控有效!
表达式使用”{params,returnObj}”,表示将入参和返回值打印出来,这里没有入参,result中的第一个@Object为空
方法内部调用路径,并输出方法路径上的每个节点上耗时
trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
查看调用increment方法的链路耗时
也可以使用通配符监控所有方法
查看JVM已加载的类信息
“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,这个命令支持的参数有 [d]、[E]、[f] 和 [x:]。
打印出了类的详细信息,看case里面,通过这种方法可以更好的判断NoSuchMethodError的原因
反编译指定已加载类的源码
jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;
加载外部的.class文件,redefine jvm已加载的类。
注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field),参考jdk本身的文档。
这个命令可以结合上面的jad命令一起使用,当需要调试正在运行的程序时,先jad反编译,然后修改了再redefine上去
这也是根据里面的case学到的
背景:
随着应用越来越复杂,依赖越来越多,日志系统越来越混乱,有时会出现一些奇怪的日志,比如:
这个程序是一个短信发送程序,由于连接不上ISMG网关而报的错误,使用的短信开发包比较老,是直接在控制台打印的,都没有使用日志,有点坑,这样看起来就比较烦了,都不知道从哪里打印的这个,找源码又太慢,现在使用Arthas来解决它
在java代码里,字符串拼接基本都是通过StringBuilder来实现的,所以把StringBuilder复制下来,然后在toString方法中修改一下
@Override public String toString() { // Create a copy, don't share the array String result = new String(value, 0, count); if(result.contains("Connection refused")) { System.err.println(result); new Throwable().printStackTrace(); } return result; }
这里加上一个判断,为异常字符串中的就打印出堆栈
然后javac StringBuilder.java编译
还是attach上这个程序后,通过redefine命令去修改StringBuilder字节码
程序的运行也发生改变,这个功能还是很强大,都可以运行中动态调试程序了
—–
wget https://alibaba.github.io/arthas/arthas-demo.jar java -jar arthas-demo.jar
arthas-demo 是一个简单的程序,每隔一秒生成一个随机数,再执行质因式分解,并打印出分解结果。
arthas-demo 源代码:
在命令行下面执行(使用和目标进程一致的用户启动,否则可能attach失败):
wget https://alibaba.github.io/arthas/arthas-boot.jar java -jar arthas-boot.jar
选择应用java进程:
$ $ java -jar arthas-boot.jar * [1]: 35542 [2]: 71560 arthas-demo.jar
Demo进程是第2个,则输入2,再输入 回车/enter 。Arthas会attach到目标进程上,并输出日志:
[INFO] Try to attach process 71560 [INFO] Attach process 71560 success. [INFO] arthas-client connect 127.0.0.1 3658 ,—. ,——. ,——–.,–. ,–. ,—. ,—. / O / | .–.”–. .–‘| ‘–‘ | / O / ‘.-‘ | .-. || ‘–‘.’| | | .–. || .-. |`. `-. | | | || |/ / | | | | | || | | |.-‘ | `–‘`–‘`–‘‘–‘`–‘ `–‘`–‘`–‘`–‘`—–‘ wiki: https://alibaba.github.io/arthas version: 3.0.5.20181127201536 pid: 71560 time: 2018-11-28 19:16:24 $
输入 dashboard ,按 回车/enter ,会展示当前进程的信息,按 ctrl+c 可以中断执行。
$ dashboard ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON 17 pool-2-thread-1 system 5 WAITIN 67 0:0falsefalse 27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0falsetrue 11 AsyncAppender-Worker-asystem 9 WAITIN 0 0:0falsetrue 9 Attach Listener system 9 RUNNAB 0 0:0falsetrue 3 Finalizer system 8 WAITIN 0 0:0falsetrue 2 Reference Handler system 10 WAITIN 0 0:0falsetrue 4 Signal Dispatcher system 9 RUNNAB 0 0:0falsetrue 26 as-command-execute-dae system 10 TIMED_ 0 0:0falsetrue 13 job-timeout system 9 TIMED_ 0 0:0falsetrue 1 main main 5 TIMED_ 0 0:0falsefalse 14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0falsefalse 18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0falsefalse 23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0falsefalse 15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0falsefalse Memory used total max usage GC heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4 ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166 ps_survivor_space 4M 5M 5M s) ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0 nonheap 20M 23M -1 gc.ps_marksweep.time( 0 code_cache 3M 5M 240M 1.32% ms) Runtime os.name Mac OS X os.version 10.13.4 java.version 1.8.0_162 java.home /Library/Java/JavaVir tualMachines/jdk1.8.0 _162.jdk/Contents/Hom e/jre
thread 1 会打印线程ID 1的栈,通常是main函数的线程。
$ thread 1 | grep‘main(‘ at demo.MathGame.main(MathGame.java:17)
$ jad demo.MathGame ClassLoader: +-sun.misc.Launcher$AppClassLoader@3d4eac69 +-sun.misc.Launcher$ExtClassLoader@66350f69 Location: /tmp/arthas-demo.jar /* * Decompiled with CFR 0_132. */ packagedemo; importjava.io.PrintStream; importjava.util.ArrayList; importjava.util.Iterator; importjava.util.List; importjava.util.Random; importjava.util.concurrent.TimeUnit; publicclassMathGame{ privatestaticRandom random =newRandom(); privateintillegalArgumentCount =0; publicstaticvoidmain(String[] args)throwsInterruptedException{ MathGame game =newMathGame(); do{ game.run(); TimeUnit.SECONDS.sleep(1L); }while(true); } publicvoidrun()throwsInterruptedException{ try{ intnumber = random.nextInt(); List<Integer> primeFactors =this.primeFactors(number); MathGame.print(number, primeFactors); } catch(Exception e) { System.out.println(String.format(“illegalArgumentCount:%3d, “,this.illegalArgumentCount) + e.getMessage()); } } publicstaticvoidprint(intnumber, List<Integer> primeFactors){ StringBuffer sb =newStringBuffer(“”+ number +“=”); Iterator<Integer> iterator = primeFactors.iterator(); while(iterator.hasNext()) { intfactor = iterator.next(); sb.append(factor).append(‘*’); } if(sb.charAt(sb.length() –1) ==‘*’) { sb.deleteCharAt(sb.length() –1); } System.out.println(sb); } publicList<Integer>primeFactors(intnumber){ if(number <2) { ++this.illegalArgumentCount; thrownewIllegalArgumentException(“number is: “+ number +“, need >= 2″); } ArrayList<Integer> result =newArrayList<Integer>(); inti =2; while(i <= number) { if(number % i ==0) { result.add(i); number /= i; i =2; continue; } ++i; } returnresult; } } Affect(row-cnt:1) cost in970ms.
通过 watch 命令来查看 demo.MathGame#primeFactors 函数的返回值:
$ watch demo.MathGame primeFactors returnObj Press Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) costin107 ms. ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[ @Integer[5], @Integer[47], @Integer[2675531], ] ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[ @Integer[2], @Integer[5], @Integer[317], @Integer[503], @Integer[887], ] ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[ @Integer[2], @Integer[2], @Integer[3], @Integer[3], @Integer[31], @Integer[717593], ] ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[ @Integer[5], @Integer[29], @Integer[7651739], ]
更多的功能可以查看 进阶使用 。
如果只是退出当前的连接,可以用 quit 或者 exit 命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出arthas,可以执行 shutdown 命令。