一. 前言
今天说的是阿里开源的Jvm调优工具Arthas, 这个工具的强大之处应该都有所耳闻了. 它也不仅仅只是一个调优工具, 有非常多强大的功能. 比如热加载类, 动态更新内存变量等等.
二. Jdk自带的调优命令
说起调优工具, Jdk自带的命令也必不可少, 不过这里只简单的记录一下, 不是本文的重点. 因为真正在观察Jvm运行情况的时候, Arthas要方便的多.
查看当前有哪些java程序正在运行: jps
jps
查看Jvm内存情况命令: jmap
① 查看内存占用情况及实例个数:jmap -histo 进程id
可以选择输入到文件(jmap -histo 进程id > test.txt)后再打开查看.
② 查看堆信息:jmap -heap 进程id
③ 导出dump文件:jmap ‐dump:format=b,file=test.hprof 进程id
format: 格式化, file: 输出文件. 可以通过jvisualvm
打开, jvisualvm也是jdk自带的工具(图形化的), 如果有配置jdk环境变量的话, 可以在windows的cmd上直接输入jvisualvm打开.
④ 除了主动导出dump文件之外, 也可以配置如下Jvm参数导出:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./
配置之后, Jvm会在堆内存溢出时, 导出dump文件, 方便后续排查导致内存溢出的故障查看Jvm运行信息: jinfo
① 启动参数:jinfo -flag 进程id
② 运行环境信息:jinfo -sysprops 进程id
查看Jvm堆栈信息: jstack
① 查找死锁或查看线程运行情况:jstack 进程id
② 查看某一个线程的堆栈信息, 此处列出查找占用CPU最高的线程:1. top -p 进程id // 列出指定进程的信息 2. 按下 H (大写) // 列出该进程的所有线程 3. 将CPU占用最高的线程id转为16进制, 因为java的线程id是用16进制来表示的, top把它转为了十进制展示. 如: 28071 = 6da7 4. jstack 进程id | grep -A 20 6da7 //可以打印出该线程正在执行的近20行代码
- 查看垃圾收集统计: jstat
jstat -gc 进程id
这个比较重要, 放个图:除了最后一个之外, 其它的没有截图, 请自行尝试. 上述的所有命令都在jdk/bin目录下, 并且都可以通过 -h 查看帮助, 如: jmap -h
三. Arthas工具
千呼万唤始出来, 终于到Arthas了. 前面的命令可以了解一下即可, 重点记住这个工具就行, 我也不会写的太多, 只会列出几个常用的命令, 因为官网的在线教程做的真是天衣无缝~
附上Arthas官网地址: http://arthas.gitee.io/
下面一起来看看常用的功能吧(仅记录, 不做初级使用教程, 入门请看官网.):
支持了一些基础命令, 如cat, pwd, grep, echo, history等, 了解Linux同学的应该会很清楚这些命令. 对了, 同样支持 “ > ”, 将输出的内容追加到某一个文件中. 还有一个session, 可以查看当前进入的java进程号.
vmoption: 查看Jvm的诊断参数, 默认查看全部.
(1) 可以通过加上参数名只看某一个, 如:vmoption PrintGCDetails
(2) 更神奇的是可以通过此参数修改它:vmoption PrintGCDetails true
thread: 查看Jvm中线程的资源占用情况
(1) 可以通过 -n 参数查看指定个数最忙的线程:thread -n 3
, 会直接打印出线程堆栈, 不必再像之前原生的Jvm参数jstack那样找来找去最后还要进行进制转换了. 也可以通过thread -n 3 -i 1000
查看1秒中最忙的3个线程
(2) 而直接使用thread
命令, 会打开一个类似top命令一样的界面, 不同的是它展示的是当前java进程的线程占用资源情况.
(3)thread -b
查看死锁情况或锁阻塞情况. 或者通过thread --state WAITING
命令查看指定状态的线程, 阻塞的状态是:BLOCKED
强大的ognl命令. 通过Arthas的ognl命令可以动态执行java代码, 或者修改一些变量的值. 这是一个非常厉害的特性, 应用场景...在下没有想到, 反正当代码发布到正式环境之后, 一切都显得阻碍重重.
(1) 动态执行代码:ognl '@java.lang.System@out.println("hello ognl")'
调用System类的out.println方法. 意思是, 使用类名全路径, 再写上自己的方法名, 就可以调用了.
(2) 查看静态变量的值:
ognl -c 类加载器hash @com.example.demo.arthas.user.UserController@logger
; 这个命令需要加上类的类加载器hash值, 所以需要先使用sc -d 类路径
查找出加载该类的类加载器, 如:
sc -d com.example.demo.arthas.user.UserController | grep classLoaderHash
可以加上 -x 参数指定需要展开的层数(当一个静态变量为对象, 且又包含多个对象时使用)
(3) ognl表达式, 官方例子:
ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
通过表达式, 声明value1和value2两个临时变量并赋值, 返回一个List. 更多的我没有去了解, 因为没有用到, 估计学了也忘的快.查看Jvm信息:
Jvm
, 可以查看到堆内存的分配, 以及gc情况(包括gc算法)dump堆内存信息:
heapdump /tmp/dump.hprof
查看Jvm监控面板:
dashboard
, 可以看到线程的资源占用情况, 以及堆内存占用情况和GC情况.快速查看方法运行是否正常:
tt -t -n 10 demo.MathGame primeFactors
-n 10 : 打印10次查看某个方法的执行所消耗的时间:
trace demo.MathGame run -n 10
如果需要过滤可以加上表达式:trace demo.MathGame run -n 10 '#cost > 10'
; 如果想要观察多个类的多个方法的执行(一般是排查调用链), 可以使用表达式:trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
四. Arthas热更新类
这个用的最多, 单独拎出来写. 热更新一个类有多种做法:
- 方式一: 直接在本地将类编译好, 上传到服务器上, 然后进入Arthas执行命令:
redefine /tmp/TestDemo.class
即可.
(1) 不过redefine之后的类是不能复原的, 这意味着...如果你为了改一个bug, 却出现了两个bug...需要再次将本地代码还原, 编译, 上传, 然后再次redefine才行.
(2) 又不过好在Arthas提供了另一个命令:retransform /tmp/TestDemo.class
, retransform热加载的类就能够还原.
(3) 通过retransform -l
可以看到通过retranform命令加载的类列表
(4) 通过retransform -d id
移除指定的热加载进来的类. 此处的id是通过上一条命令查询到的. 也可以通过retransform --deleteAll
删除所有热加载进来的类.
(5) 注意! 执行上方移除后, 热加载的类仍在Jvm内存中, 需要重新执行一次retransform命令:retransform --classPattern com.test.TestDemo
, 这样才能还原.
(6) PS: 我个人还是比较偏向redefine
, 本地做好测试即可.
还有!!! 不论redefine还是retransform, 都不能热更新更改了方法签名或类成员变量的类. 方法签名: 方法的作用域, 返回值, 方法名, 方法参数- 方式二: 官网的标准流程, 中间步骤可根据情况省略:
(1)jad --source-only com.TestDemo > /tmp/TestDemo.java
将要热更新的类反编译至一个java文件中
(2) 修改该文件中的java代码
(3)sc -d *UserController | grep classLoaderHash
找到该类的类加载器的hash值, 一般打印如下:classLoaderHash 1be6f5c3
(4)mc -c 1be6f5c3 /tmp/TestDemo.java -d /tmp
将修改好的java文件编译到指定目录. -c 指定类加载器; -d 指定编译目录
(5) 使用redefine
或retransform
命令热更新该类
全文结束, 不过还是希望小伙伴儿们去官网在线尝试一下, 绝对受益匪浅. 然后也欢迎指正~