【死磕 JVM】用 Arthas 排查 JVM 内存 真爽!我从小用到大
Arthas 是啥
当我们系统遇到 JVM 或者内存溢出等问题的时候,如何对我们的程序进行有效的监控和排查,就发现了几个比较常用的工具,比如 JDK 自带的 jconsole、jvisualvm
还有一个最好用的工具—— jprofiler
,但是这个是收费的,或者除了很有钱的公司,一般很少人会用这个,还有一个就是我们今天的主角—— Arthas ,为什么今天会重点讲这个呢?
官网地址: http://arthas.gitee.io/
GitHub 地址: https://github.com/alibaba/arthas/
Arthas 是 Alibaba 开源的 Java 诊断工具,采用命令行交互模式,提供了较为丰富的功能,主要还是他是免费里面的算是好用且功能比较强大的一个 JVM 排查的插件,在了解这个利器之后,发现还是挺好用的,而且支持的功能也比较全面,那么 Arthas 到底可以为我们做哪些事情呢?
提供性能看板,包括线程、cpu、内存等信息,并且会定时的刷新。
根据各种条件查看线程快照。找出 cpu 占用率最高的 n 个线程
输出 jvm 的各种信息,如 gc 算法、jdk 版本、ClassPath 等
遇到问题无法在线上 debug,热部署加日志直接替换
查看某个类的静态属性,也可以通过 ognl 语法执行一些语句
查看已加载的类的详细信息,这个类从哪个 jar 包加载的,查看类的方法的信息
dump 类的字节码到指定目录
直接反编译指定的类
快速定位应用的热点,生成火焰图
可以监控到 JVM 的实时运行状态
以前,你碰到这些问题,解决的办法大多是,修改代码,重新上线。但是在大公司里,上线的流程是非常繁琐的,如果为了多加一行日志而重新发布版本,无疑是非常折腾人的。但是阿里巴巴开源的 Arthas
有了更为优雅的线上调试方法。
Arthas 支持 JDK6,同时可以在 Linux/Mac/Windows
上运行,自动 Tab 补全功能,更方便我们定位问题和诊断
下载地址: https://arthas.gitee.io/download.html
你可以下载 zip 的包我下载的是 arthas-packaging-3.5.0-bin.zip
或者通过命令去下载
wget https://alibaba.github.io/arthas/arthas-boot.jar
使用手册
1. 快速启动
当我们下载好之后,我们直接通过命令启动就可以 java -jar arthas-boot.jar
,但是在此之前我们需要通过检测的代码来挂靠到 Arthas 上面
这个是上篇文章讲述的案例,感兴趣的可以了解一下。
首先我们需要使用 javac 命令将 Java 文件进行编译 javac FullGCTest.java 进行编译,然后打印 GC 日志,进行风险监控打印 GC 日志:
java -Xms200M -Xmx200M -XX:+PrintGC FullGCTest
Arthas 启动命令: java -jar arthas-boot.jar
我们就看到了我们刚才启动的 FullGCTest 的应用程序,我们输入编号 1 回车,这样我们就把 Arthas 挂靠到我们的程序上,接下来我们只需要做对应的命令操作就可以了
命令详情文档: https://arthas.aliyun.com/doc/commands.html
2. 功能列表
命令
详细说明
jvm
查看当前 JVM 信息
thread
查看当前 JVM 的线程堆栈信息
watch
方法执行数据观测
dashboard
当前系统的实时数据面板
trace
方法内部调用路径,并输出方法路径上的每个节点上耗时
stack
输出当前方法被调用的调用路径
tt
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
vmoption
查看,更新 JVM 已加载的类信息
sc
查看 JVM 已加载的类信息
sm
查看已加载类的方法信息
jad
反编译指定已加载类的源码
classloader
查看 classloader 的继承树,urls,类加载信息
heapdump
类似 jmap 命令的 heap dump 功能
OPERATING-SYSTEM:系统相关参数
THREAD 相关:
COUNT : JVM 当前活跃的线程数
DAEMON-COUNT : JVM 当前活跃的守护线程数
PEAK-COUNT: 从 JVM 启动开始曾经活着的最大线程数
STARTED-COUNT: 从 JVM 启动开始总共启动过的线程次数
DEADLOCK-COUNT: JVM 当前死锁的线程数
MEMORY
FILE-DESCRIPTOR(文件描述符相关):
MAX-FILE-DESCRIPTOR-COUNT:JVM 进程最大可以打开的文件描述符数
OPEN-FILE-DESCRIPTOR-COUNT:JVM 当前打开的文件描述符数
thread 命令
参数说明:
命令
详细说明
id
线程 id
[n:]
指定最忙的前 N 个线程并打印堆栈
[b]
找出当前阻塞其他线程的线程
[i]
指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200
[–all]
显示所有匹配的线程
打印当前最忙的 N 个线程并打印堆栈
thread -n 3
thread 查看所有线程
thread 17: 显示指定线程的运行堆栈
thread -i:指定采样时间间隔
thread -i 1000 : 统计最近 1000ms 内的线程 CPU 时间。
thread -n 3 -i 1000 : 列出 1000ms 内最忙的 3 个线程栈
dashboard 命令
运行程序时,会显示当前程序的实时信息,如 qps, rt, 错误数, 线程池信息等等
数据说明:
ID: Java 级别的线程 ID
NAME: 线程名
GROUP: 线程组名
PRIORITY: 线程优先级, 1~10 之间的数字,越大表示优先级越高
STATE: 线程的状态 CPU%: 线程的 cpu 使用率。比如采样间隔 1000ms,某个线程的增量 cpu 时间为 100ms,则 cpu 使用率=100/1000=10%
DELTA_TIME: 上次采样之后线程运行增量 CPU 时间,数据格式为秒
TIME: 线程运行总 CPU 时间,数据格式为分:秒
INTERRUPTED: 线程当前的中断位状态
DAEMON: 是否是 daemon 线程
参数说明:
参数名称
详细说明
id
刷新实时数据的时间间隔 (ms),默认 5000ms
[n:]
刷新实时数据的次数
sc 命令
查看 JVM 已加载的类信息,通过 SC 我们可以看到我们这个类的详细信息,包括是从哪个 jar 包读取的,他是不是接口/枚举类等,甚至包括他是从哪个类加载器加载的。
参数说明:
参数名称
详细说明
class-pattern
类名表达式匹配
method-pattern
方法名表达式匹配
[d]
输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的 ClassLoader 等详细信息。如果一个类被多个 ClassLoader 所加载,则会出现多次
[E]
开启正则表达式匹配,默认为通配符匹配
sc -d *CardInfo
: 打印类的详细信息
sc -d -f *CardInfo
:打印类的 Fiedld 信息
heapdump + jhat 分析
heapdump:类似于 jmap 命令
创建到指定文件夹下:
创建成功后,我们就可以在指定文件夹下看到对应的 dump 文件,然后使用命令 jhat dump.hprof
,生成文件,成功后我们就可以通过 IP+端口进行访问了
访问:
然后我们就可以通过 IP+端口去访问它了,里面有个他的 other,我们拉到最底下,找
Show instance counts for all classes (including platform)
从下面我们可以分析出来哪个类包含的对象最多,分析出来哪个类产生的对象
这个里面最强大的功能还是叫做 Execute Object Query Language (OQL) query
,这个里面可以显示有哪些对象,对象有多少个字节和引用,可以观察到哪个对象产生了问题,如下图所示,显示所有 String 对应的对象
搜索点进去之后我们还能看到这个对象到底占用了多少个字节,有多少个引用指向了这个 Object,这个 OQL 的语法也是很灵活,我们可以使用 where 条件去过滤
jad:反编译某个类,或者反编译某个类的某个方法,动态代理生成类的问题定位 第三方的类(观察代码) 版本问题(确定自己最新提交的版本是不是被使用)
有人可能会问这个有啥用,源码我不是自己就知道吗?因为有时我们经常会不确定线上或者测试环境的包是否是我们修改过的,这时候就可以通过 jad 反编译来看下,是否是最新的代码
redafine
redafine:热替换,动态更新代码,不用重启 jvm 目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性 m() -> mm()
比如我们在线上环境有个 class 确认有问题,想要重新替换,一般情况下只能停掉服务器重新发布,在普通的小公司这样是可以的,但是在大规模公司京东淘宝这样的是不能停的,因为整个流程是非常复杂的,那怎么办呢?大家可以看到下面的案例
首先我们新建一个测试案例:
当我们输入 a 的时候打印 2,但是我们上线以后才发现,我们需要输出的 1,这个是如果要从本地更改要重新发布上线,为了这一个修改,明显是不值当的,但是如果我们用 redafine 热部署就可以帮助我们直接替换,不用重新发布 jvm
然后我们将 T 这个程序挂靠到 Arthas 上面去
然后我们直接修改 TT.java 程序 vi TT.java
,将里面打印 2 的值修改成 1
然后编译执行 ```javac TT.java ````
在回到我们挂靠的 Arthas 上面执行 redefine /usr/local/mxn/fuccGc/t/TT.class
执行成功
大家可以看到我们在没有重新启动的情况下成功替换了 class 文件
watch:方法执行的数据观测,可以通过 watch 指令,来监控某个类,监控后,运行下你的功能,复现下场景,arthas 会提供给你具体的出参和入参,帮助你排查故障
输出方法调用路径,并输出耗时,这个指令对于优化代码非常的有用,可以看出具体每个方法执行的时间,如果是 for 循环等重复语句,还能看出 n 次循环中的最大耗时,最小耗时,和平均耗时,完美!
在我们对某个方法开启 tt 后,会记录每一次调用(我们可以设置最大监控次数)的入参和返回参数,并能对这些不同时间下调进行观测
[arthas@405136]$ tt -t FullGCTest modelFit
命令参数解析-t
tt 命令有很多个主参数,-t 就是其中之一。这个参数的表明希望记录下类 *Test 的 print 方法的每次执行情况。
-n 3
当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。
此时你可以通过 -n 参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断 tt 命令的记录过程,避免人工操作无法停止的情况。
ognl 表达式
ognl 表达式
OGNL 特殊用法请参考: https://github.com/alibaba/arthas/issues/71
OGNL 表达式官方指南: https://commons.apache.org/proper/commons-ognl/language-guide.html
调用静态函数: ognl '@java.lang.System@out.println("hello")'
获取静态类的静态字段: ognl '@FullGCTest@random'
Arthas 还支持 Web Console,详见: https://alibaba.github.io/arthas/web-console.html
Arthas 是一个线上 Debug 神器,相比于其他工具,Arthas 有着比较全面的功能,上手也比较容易,对于刚开始入门的小伙伴也是可以轻松掌握的
评论