前言
我们在定位java 一些问题的时候,经常会用到很多jdk的命令例如垃圾回收回收jstat ,查看堆内存空间jmap ,查看线程jstack这些命令。Arthas 除了对上述命令能够更加方便获取当前运行环境的情况,还能一些更加强大的功能。
实践
首先我们需要准备一个运行当前已经在运行java 项目,如果实在没有java 项目可以用下面demo项目替代。是一个很简单的程序,它随机生成整数,再执行因式分解,把结果打印出来。如果生成的随机数是负数,则会打印提示信息。
wget https://arthas.aliyun.com/arthas-demo.jar;java -jar arthas-demo.jar
我们自己项目会有一下内存方面设置,更加贴近于实际,这里我这里就用自己写的一个web的小demo项目测试。
当我们启动好项目,通过下面命令下载arthas-boot,并通过java -jar arthas-boot.jar 启动就好了
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
arthas-boot是Arthas的启动程序,它启动后,会列出所有的Java进程,用户可以选择需要诊断的目标进程。
选择对应进程,例如第一个进程输入 1 ,再Enter/回车
Dashboard
bashboard
通过这个大屏幕我们可以主要关注的几个点是,
第一大行存在当前最cpu占用率最高的线程及及对应进程
如果我们发现这个线程有异常那么可以,其中一个方法可以(另外一个方法可以通过下面 thread 线程id),另开一个窗口通过下面这个命令获取当前cpu消耗最大的线程并获取其线程情况。我的当前进程id为2010700,下面一个命令是
打印当前线程情况,及获取当前线程消耗cpu最高线程列表。(之所以要一起做是因为两者,很多线程结束很快)
jstack 2010700 >stack.log && top -Hp 2010700
将最靠前两个线程转化为16进制(因为线程打印出来是通过16进制表示的)
printf "%x\n" 2010705
然后将去刚刚生成stack.log 中搜索打印出来进程的具体情况
第二大行当前各个分区所占内存比例,垃圾回收方法,及当前回收方法次数
由上图可知,我当前这个demo存在,eden 区64M,survivor 16384k,old区 32M。code_cache 16M,而且老年代回收次数还大于年轻代回收次数,可以知道当前内存分配是极不合理的。
thread 线程id
thread 58
通过上面大屏我们可以知道当前线程的的线程id,我们再通过上面线程id获取到这个线程的堆栈信息
Jad
我们可能会遇到一个问题是当前运行的代码不是正在运行最新代码,那么我们可能通过jad进行反编译确认当前的代码是否最新代码。下面支持 管道,如果一个类代码过多通过grep 方式进行过滤。
jad com.yin.web.controller.TestController | grep step
jad com.yin.web.controller.TestController
查看一个方法入参和出参
我们经常可能遇到一个方法无法判断入参出参是自己想要的这个时候,我们监听一个入参出参的值以及耗时情况
#watch 抓取数据,当为引用地址时候打印为地址。这个时候可以使用options json-format true 更改json格式化内容
options json-format true
watch com.yin.web.controller.TestController getData "{params,returnObj}" -x 1
输入命令之后,可以自己手动调用,这个时候每当调用这个方法就将打印出对应参数值
获取java 获取耗时
#trace 类 方法
trace com.yin.web.controller.TestController getData
获取一个方法的耗时
动态更新日志级别
#获取当前日志级别,及classLoaderHash 值的信息
logger
logger --name root --level debug -c 49c2faae
热更新代码
我们在很多时候可能需要修改一两行代码,验证情况但如果本地修改编译运行这样情况实在过于麻烦,那么如果动态修改当前运行的类呢?
1 Arthas 界面中使用 sc 命令加 -d 参数指定全限定名查找 TestController 类的相关信息,得到加载 Demo 类 ClassLoader 对应的 Hash 值
sc -d com.yin.web.controller.TestController | grep classLoaderHash | head -n1
2.我手动上传我需要修改变成TestController 类到/usr/local/arthas(根据用户具体情况决定上传目录) 这个目录。
3 Arthas 界面中使用 mc 命令内存编译TestController 类,并通过 -c 参数指定由上一步获取的ClassLoader 的 hash 值( -d 参数为编译后文件输出目录)
#mc -c hash值 需要生效的java文件 -d 编译生成java文件
mc -c 49c2faae /usr/local/arthas/TestController.java -d /usr/local/arthas/
4、通过上一步会在得到编译后的 TestController.class 文件后(可能会有匿名类TestController$1.class文件,如果没有修改匿名类逻辑,不需要热更新), 就可以用 retrasform 命令热更新代码了(class文件路径为上一步中输出的/usr/local/arthas/目录com/yin/web/controller/TestController.class )
#retransform 编译之后class文件
retransform /usr/local/arthas/com/yin/web/controller/TestController.class
有些时候 retransform 并不能成功,这个时候可以使用
redefine -c 49c2faae /usr/local/arthas/com/yin/web/controller/TestController.class
retransform 和 redefine 不同的时候,retransform 可以通过接下来命令动态恢复,但redefine 需要恢复必须要重启应用才能恢复
5、查看有多少个热加载更新的class,同一个就是id 增加的,删除之后,就是按最大Id也就是最近替进去的执行
retransform -l
这个时候就可以调试我们修改之后的代码了
,当我们修改完成之后我们就可以根据下面的方法恢复原来的运行结果
#使用 retransform 命令撤销更新
retransform --deleteAll
#要想显示的去生效旧版,还要再执行下面的指令才能完全恢复
retransform --classPattern com.yin.web.controller.TestController