带着8个问题5分钟教你学会Arthas诊断工具 àì夳堔傛蜴生んèń 2022-09-04 09:59 116阅读 0赞 点击上方蓝色“石杉的架构笔记”,选择“设为星标” 回复“PDF”获取独家整理的学习资料! ![6675b893df1b6dccce899248abf9b95f.png][] **![5a8319c4dd6cb4a6f9c31b22393e9f82.png][]** ![56092c2f99797c49606b827dac5ed83a.png][] 长按扫描上方一元购买 ![c5c6348e56c27534d9d4376821ba7c3a.png][] ## 前言 ## `Arthas` 是Alibaba开源的Java诊断工具,深受开发者喜爱。 当你遇到以下类似问题而束手无策时,`Arthas`可以帮助你解决: 1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? 2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? 3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? 4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! 5. 是否有一个全局视角来查看系统的运行状况? 6. 有什么办法可以监控到JVM的实时运行状态? 7. 怎么快速定位应用的热点,生成火焰图? 8. 怎样直接从JVM内查找某个类的实例? 这 8 个问题,Arthas 官方文档(`https://arthas.aliyun.com/doc`)中并没有给出答案或标准的解决方案。 ![24ae400d529bc150bedbc2f2866e3c41.png][] 坑爹啊 这不是管杀不管埋吗!!! ![35859369906d63e4c8ff9b4f8b82dd5b.png][] 管杀不管埋 ## 正文 ## **「下面是笔者结合多年使用 Arthas 的经验,针对这 8 个问题给出的详细解决方案,如果有疑问欢迎评论区指出。」** ## 准备 ## ### 先给出我的测试代码 ### package com.shockang.study; import com.alibaba.fastjson.JSON; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.ToString; import lombok.experimental.FieldDefaults; import java.util.List; import java.util.concurrent.TimeUnit; public class ArthasDemo { public static void main(String[] args) { String s = "[{\"name\":\"zhangsan\",\"age\":\"10\",\"telephone\":\"123456\",\"interests\":[\"sing\",\"dance\",\"rap\"]},\n" + "{\"name\":\"lisi\",\"age\":\"20\",\"telephone\":\"123457\",\"interests\":[\"sing\",\"swim\"]},\n" + "{\"name\":\"wangwu\",\"age\":\"30\",\"telephone\":\"123458\",\"interests\":[\"sing\",\"program\"]}]"; //模拟一遍遍的调用方法的过程 for (; ; ) { System.out.println(new ArthasDemo().convert(s)); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } private List<People> convert(String s) { return JSON.parseArray(s, People.class); } @Getter @Setter @ToString @FieldDefaults(level = AccessLevel.PRIVATE) private static class People { /** * 姓名 */ String name; /** * 年龄 */ String age; /** * 电话 */ String telephone; /** * 兴趣列表 */ List<String> interests; } } #### 以下是控制台正常打印的结果 #### /Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/bin/java ... [ArthasDemo.People(name=zhangsan, age=10, telephone=123456, interests=[sing, dance, rap]), ArthasDemo.People(name=lisi, age=20, telephone=123457, interests=[sing, swim]), ArthasDemo.People(name=wangwu, age=30, telephone=123458, interests=[sing, program])] [ArthasDemo.People(name=zhangsan, age=10, telephone=123456, interests=[sing, dance, rap]), ArthasDemo.People(name=lisi, age=20, telephone=123457, interests=[sing, swim]), ArthasDemo.People(name=wangwu, age=30, telephone=123458, interests=[sing, program])] ### 下载并运行 Arthas ### 按照下图中的步骤,选择一个 Java 进程进行 attach。 ![096ff55fcb5e78714f4756aad81af5b4.png][] 下载并运行Arthas ### 访问 WebConsole ### attach 成功后可以打开谷歌浏览器输入`http://127.0.0.1:3658/` 打开 WebConsole (吐槽一句 Mac OS 的 Safari 浏览器不支持) > ❝ > > 使用 WebConsole 最方便的是你可以打开多个标签页同时操作 > > ❞ ## 问题 1:这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? ## 这个问题我经常在处理各种**「依赖冲突」**的时候遇到,有一些类的完全名称是一模一样,通过常规的办法无法解决类具体从哪个 jar 包加载。 别急,看我下面的解决办法。 1. sc 通过 `sc` 命令 模糊查看当前 JVM 中是否加载了包含关键字的类,以及获取其完全名称。 > ❝ > > 注意使用 `sc -d` 命令,获取 classLoaderHash,这个值在后面需要用到。 > > ❞ sc -d *ArthasDemo* ![15512ebf406148508617ec2b3c3cd9e1.png][] sc-d命令 1. classloader 通过 `classloader` 查看 class 文件来自哪个 jar 包 > ❝ > > 使用 `cls` 命令可以清空命令行,这个简单的命令官方文档居然找不到。。。 > > ❞ > ❝ > > 注意 `classloader -c` 后面的值填上面第一步中获取到的 Hash 值,class 文件路径使用'/'分割,且必须以.class 结尾。 > > ❞ [arthas@3633]$ classloader -c 18b4aac2 -r com/shockang/study/ArthasDemo.class file:/Users/shockang/code/concurrentbook/target/classes/com/shockang/study/ArthasDemo.class Affect(row-cnt:1) cost in 0 ms. 上面是显示 class 文件路径的,如果 class 文件来自 jar 包,可以显示 jar 包路径,例如官方文档给的例子: $ classloader -c 1b6d3586 -r java/lang/String.class jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/String.class ## 问题 2:我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? ## 推荐使用 `watch` 和 `tt` 命令,非常好用。 这两个命令都是用来查看方法调用过程的,不同的是 `watch` 命令是调用一次打印一次方法的调用情况,而 `tt` 命令可以先生成一个不断增加的调用列表,然后指定其中某一项进行观测。 1. 使用 `watch` 命令查看方法调用情况。我们要查看 ArthasDemo 这个类里面的 convert 方法调用情况。 ![0e61619c2a0be0e5ebc5afd25b67ade3.png][] watch命令 watch com.shockang.study.ArthasDemo convert "{params,target,returnObj}" -f -x 4 `watch` 后面跟上完全类名和方法名,以及一个 OGNL 的表达式,-f 表示不论正常返回还是异常返回都进行观察,-x 表示输出结果的属性遍历深度,默认为 1, > ❝ > > 建议无脑写 4 就行,这是笔者经验来看最大的遍历深度,再大就不支持了 > > ❞ 1. 使用 `tt` 命令来观测方法调用情况,`tt` 命令可以查看**「多次调用」**并选择其中一个进行观测,但是如果输出结果是多层嵌套就没办法看了,而 `watch` 可以查看**「多层嵌套」**的结果。 > ❝ > > 使用 tt -t 记录下当前方法的每次调用环境现场 > > ❞ ![8ebf42ce4a0b8c7c811a0d78c3f5bcd5.png][] tt -t命令 tt -t com.shockang.study.ArthasDemo convert TIMESTAMP表示方法调用发生的时间,COST 表示调用耗时(ms),IS-RET表示是否正常返回,IS-EXP 表示是否异常返回,OBJECT 表示对象的 HASH 值 > ❝ > > 对于具体一个时间片的信息而言,你可以通过 -i 参数后边跟着对应的 INDEX 编号查看到他的详细信息 > > ❞ ![a5671c6b105f8a1cf76946fc6c388ce2.png][] tt-i命令 > ❝ > > 图中之所以可以打印兴趣列表,是调用了其 toString 方法,如果没有重写 java.lang.Object 类的 toString 方法,只会看到 hash 值。 > > ❞ 1. 如何判断代码是否已经提交? 通过 `jad --source-only` 可以查看源代码。 [arthas@3633]$ jad --source-only com.shockang.study.ArthasDemo /* * Decompiled with CFR. */ package com.shockang.study; import com.alibaba.fastjson.JSON; import java.util.List; import java.util.concurrent.TimeUnit; public class ArthasDemo { public static void main(String[] args) { /*15*/ String s = "[{\"name\":\"zhangsan\",\"age\":\"10\",\"telephone\":\"123456\",\"interests\":[\"sing\",\"dance\",\"rap\"]},\n{\"name\":\"lisi\",\"age\":\"20 \",\"telephone\":\"123457\",\"interests\":[\"sing\",\"swim\"]},\n{\"name\":\"wangwu\",\"age\":\"30\",\"telephone\":\"123458\",\"interests\":[\"sing\",\"program\"]}]"; while (true) { /*20*/ System.out.println(new ArthasDemo().convert(s)); try { /*22*/ TimeUnit.SECONDS.sleep(10L); /*25*/ continue; } catch (InterruptedException e) { /*24*/ e.printStackTrace(); continue; } break; } } private List<People> convert(String s) { /*30*/ return JSON.parseArray(s, People.class); } private static class People { private String name; private String age; private String telephone; private List<String> interests; private People() { } public String toString() { return "ArthasDemo.People(name=" + this.getName() + ", age=" + this.getAge() + ", telephone=" + this.getTelephone() + ", interests=" + this.getIntere sts() + ")"; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getAge() { return this.age; } public String getTelephone() { return this.telephone; } public List<String> getInterests() { return this.interests; } public void setAge(String age) { this.age = age; } public void setTelephone(String telephone) { this.telephone = telephone; } public void setInterests(List<String> interests) { this.interests = interests; } } } [arthas@3633]$ ## 问题 3:遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? ## 通过上面问题 2 的 `watch` 和 `tt` 命令可以查看方法调用情况。 此外,可以通过 `redefine` 命令**「热替换」**线上的代码,注意应用重启之后会失效,这在某些紧急情况下会有奇效。 比如说我们修改一下方法体里面的代码,加了一行日志打印: private List<People> convert(String s) { System.out.println(s); return JSON.parseArray(s, People.class); } 这时我们就可以将新代码编译后的 class 文件热替换正在运行的 ArthasDemo 的代码。 ![86b2a858b343d4e8f1f3521ae3afbda2.png][] redefine命令 ![9ade56f2c04bb6f374fb459d33af1ff0.png][] 热替换 JVM 内存中(方法区)加载的类 从这张图可以明显的看出,明明源码中没有打印字符串 s 的逻辑,但是控制台还是打印了字符串,因为我们已经热替换了 JVM 内存中(方法区)加载的类。 ## 问题 4:线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! ## 这个问题没有完美的解决办法 参考一下问题 2 和问题 3的解决方案 推荐使用 `tt` 命令并将命令行返回结果输出到一个文件中,后续可以选择异常的一行记录使用 `tt -i` 命令进行深入的分析。 `tee`指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。 ![5bee26b0e9476bc3fd34b2548ac9db67.png][] tee命令 tt -t com.shockang.study.ArthasDemo convert | tee /Users/shockang/Downloads/log 此外还可以使用 `monitor` 命令统计方法调用成功失败情况。 ![2eb2f8b52d01983a1cbfce636185562e.png][] monitor命令 monitor -c 30 com.shockang.study.ArthasDemo convert | tee /Users/shockang/Downloads/log1 > ❝ > > \-c 后面接统计周期,默认值为120秒 > > ❞ ## 问题 5:是否有一个全局视角来查看系统的运行状况? ## 使用 `dashboard` 命令可以查看当前系统的实时数据面板, 当运行在Ali-tomcat时,会显示当前tomcat的实时信息,如HTTP请求的qps, rt, 错误数, 线程池信息等等。 ![e64d213286e32d9ed64add3f99b5eebd.png][] dashboard实时数据面板 从图中可以看到线程情况,内存使用情况,系统参数等。 ## 问题 6:有什么办法可以监控到JVM的实时运行状态? ## 使用 `jvm` 命令可以查看 JVM 的实时运行状态。 ![4bbba7917ad59a84c6e4f221f8569065.png][] JVM 的实时运行状态 ## 问题 7:怎么快速定位应用的热点,生成火焰图? ## `profiler` 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。 > ❝ > > 默认情况下,生成的是 cpu 的火焰图,即 event 是 cpu,可以用--event 参数来指定。注意不同系统支持的 event 不同 > > ❞ ![88e091ffa7799dd270ad0947aef329d8.png][] 默认情况下,arthas使用3658端口,则可以打开:`http://localhost:3658/arthas-output/` 查看到arthas-output目录下面的profiler结果: ![8d2c9002991215e3fa8b596d37a68811.png][] profiler目录 选择一项点击 ![b1f907c4c593cebe59663858b30252e3.png][] profiler结果图 ## 问题 8:怎样直接从JVM内查找某个类的实例? ## 使用 `vmtool` 可以达成目的 > ❝ > > 这个功能是 Arthas 3.5.1 新增的。可以参考官方文档 https://arthas.aliyun.com/doc/vmtool.html\#id1 > > ❞ $ vmtool --action getInstances --className java.lang.String --limit 10 @String[][ @String[com/taobao/arthas/core/shell/session/Session], @String[com.taobao.arthas.core.shell.session.Session], @String[com/taobao/arthas/core/shell/session/Session], @String[com/taobao/arthas/core/shell/session/Session], @String[com/taobao/arthas/core/shell/session/Session.class], @String[com/taobao/arthas/core/shell/session/Session.class], @String[com/taobao/arthas/core/shell/session/Session.class], @String[com/], @String[java/util/concurrent/ConcurrentHashMap$ValueIterator], @String[java/util/concurrent/locks/LockSupport], ] 通过 `--limit`参数,可以限制返回值数量,避免获取超大数据时对JVM造成压力。默认值是10。 如果想精确的定位到具体的类实例,可以通过指定 classloader name 或者 classloader hash,如下所示: vmtool --action getInstances --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader --className org.springframework.context.ApplicationContext vmtool --action getInstances -c 19469ea2 --className org.springframework.context.ApplicationContext > ❝ > > 获取 classloader hash 的方法请参考上面的问题 1 > > ❞ vmtool 还有个不错的功能,可以**「强制进行GC」**,这在某些生产环境内存紧张的情况下有奇效。 vmtool --action forceGc ** ** ![b8da6686bcadd5b3c0b424188e31886c.png][] [6675b893df1b6dccce899248abf9b95f.png]: /images/20220829/a4855a5fe11b4de8bab881f7f955c768.png [5a8319c4dd6cb4a6f9c31b22393e9f82.png]: /images/20220829/5ed010efa8fe43eda39152bae6825536.png [56092c2f99797c49606b827dac5ed83a.png]: /images/20220829/60d073561040423aa4ed5a39cfa4cdd2.png [c5c6348e56c27534d9d4376821ba7c3a.png]: /images/20220829/fec0006ac6a64bc887dbebfd10d21b82.png [24ae400d529bc150bedbc2f2866e3c41.png]: /images/20220829/13b8793619fb4dc0ac582e709c1fd7be.png [35859369906d63e4c8ff9b4f8b82dd5b.png]: /images/20220829/e4e8b7777ff74e8a9c77a6664d814a83.png [096ff55fcb5e78714f4756aad81af5b4.png]: /images/20220829/b5dc898e1b4a468ca02ea7bd8c8d217a.png [15512ebf406148508617ec2b3c3cd9e1.png]: /images/20220829/bd4a7e87c82447e3b0245e4c473b8346.png [0e61619c2a0be0e5ebc5afd25b67ade3.png]: /images/20220829/822c06b7890c49c9b52d39a7c1f7fc21.png [8ebf42ce4a0b8c7c811a0d78c3f5bcd5.png]: /images/20220829/d830dff5651c4c3b8344ce539cb12fe9.png [a5671c6b105f8a1cf76946fc6c388ce2.png]: /images/20220829/9c2dca35450b4b0dad9f96088cd89693.png [86b2a858b343d4e8f1f3521ae3afbda2.png]: https://img-blog.csdnimg.cn/img_convert/86b2a858b343d4e8f1f3521ae3afbda2.png [9ade56f2c04bb6f374fb459d33af1ff0.png]: /images/20220829/eee75e53a6424bafbbdc1205cb235a11.png [5bee26b0e9476bc3fd34b2548ac9db67.png]: /images/20220829/2ffdc3a687b246ffa2436cb63732afda.png [2eb2f8b52d01983a1cbfce636185562e.png]: /images/20220829/fe574f2cca2a43558c221002ffc31a59.png [e64d213286e32d9ed64add3f99b5eebd.png]: /images/20220829/87a4e55319c5434bb56da552920f99fd.png [4bbba7917ad59a84c6e4f221f8569065.png]: /images/20220829/27c19728186b4960aecb7bdfe4caec94.png [88e091ffa7799dd270ad0947aef329d8.png]: /images/20220829/3c2b1deb4e3a45f3a28cd64d8d3404d5.png [8d2c9002991215e3fa8b596d37a68811.png]: /images/20220829/be0be00d1fc44b12b17ab406c3efaf63.png [b1f907c4c593cebe59663858b30252e3.png]: /images/20220829/d9b1f5be8f824a86972e741f4c6dd5f0.png [b8da6686bcadd5b3c0b424188e31886c.png]: /images/20220829/77cd04973b1a44e0a5ae48e334241b2b.png
还没有评论,来说两句吧...