写点什么

【死磕 JVM】用 Arthas 排查 JVM 内存 真爽!我从小用到大

用户头像
牧小农
关注
发布于: 2021 年 05 月 13 日
【死磕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 到底可以为我们做哪些事情呢?


  1. 提供性能看板,包括线程、cpu、内存等信息,并且会定时的刷新。

  2. 根据各种条件查看线程快照。找出 cpu 占用率最高的 n 个线程

  3. 输出 jvm 的各种信息,如 gc 算法、jdk 版本、ClassPath 等

  4. 遇到问题无法在线上 debug,热部署加日志直接替换

  5. 查看某个类的静态属性,也可以通过 ognl 语法执行一些语句

  6. 查看已加载的类的详细信息,这个类从哪个 jar 包加载的,查看类的方法的信息

  7. dump 类的字节码到指定目录

  8. 直接反编译指定的类

  9. 快速定位应用的热点,生成火焰图

  10. 可以监控到 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 上面


import java.math.BigDecimal;import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;
public class FullGCTest {

//模拟银行卡的类 private static class CardInfo { //小农的银行卡信息记录 BigDecimal price = new BigDecimal(10000000.0); String name = "牧小农"; int age = 18; Date birthdate = new Date();
public void m() {} }
//线程池 定时线程池 //50个,然后设置 拒绝策略 private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50, new ThreadPoolExecutor.DiscardOldestPolicy());
public static void main(String[] args) throws Exception { executor.setMaximumPoolSize(50);
for (;;){ modelFit(); Thread.sleep(100); } }
/** * 对银行卡进行风险评估 */ private static void modelFit(){ List<CardInfo> taskList = getAllCardInfo(); //拿出每一个信息出来 taskList.forEach(info -> { // do something executor.scheduleWithFixedDelay(() -> { //调用M方法 info.m();
}, 2, 3, TimeUnit.SECONDS); }); }
private static List<CardInfo> getAllCardInfo(){ List<CardInfo> taskList = new ArrayList<>(); //每次查询100张卡出来 for (int i = 0; i < 100; i++) { CardInfo ci = new CardInfo(); taskList.add(ci); }
return taskList; }}
复制代码


这个是上篇文章讲述的案例,感兴趣的可以了解一下。


首先我们需要使用 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


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 命令

参数说明:



打印当前最忙的 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 线程



参数说明:


sc 命令

查看 JVM 已加载的类信息,通过 SC 我们可以看到我们这个类的详细信息,包括是从哪个 jar 包读取的,他是不是接口/枚举类等,甚至包括他是从哪个类加载器加载的。


参数说明:



sc -d *CardInfo: 打印类的详细信息



sc -d -f *CardInfo:打印类的 Fiedld 信息


heapdump + jhat 分析

heapdump:类似于 jmap 命令


创建到指定文件夹下:


[arthas@365564]$ heapdump /usr/local/mxn/dump.hprofDumping heap to /usr/local/mxn/dump.hprof ...Heap dump file created
复制代码


创建成功后,我们就可以在指定文件夹下看到对应的 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:反编译某个类,或者反编译某个类的某个方法,动态代理生成类的问题定位 第三方的类(观察代码) 版本问题(确定自己最新提交的版本是不是被使用)



有人可能会问这个有啥用,源码我不是自己就知道吗?因为有时我们经常会不确定线上或者测试环境的包是否是我们修改过的,这时候就可以通过 jad 反编译来看下,是否是最新的代码

redafine

redafine:热替换,动态更新代码,不用重启 jvm 目前有些限制条件:只能改方法实现(方法已经运行完成),不能改方法名, 不能改属性 m() -> mm()


比如我们在线上环境有个 class 确认有问题,想要重新替换,一般情况下只能停掉服务器重新发布,在普通的小公司这样是可以的,但是在大规模公司京东淘宝这样的是不能停的,因为整个流程是非常复杂的,那怎么办呢?大家可以看到下面的案例


首先我们新建一个测试案例:


public class T{    public static void main(String[] args) throws Exception{                    for(;;){                    System.in.read();                    new TT().m();                }        }}
复制代码


public class TT{        public void m(){        System.out.println(2);    }}
复制代码


使用命令javac *.java,编译成 class 文件,然后运行 T 文件


[root@VM-0-7-centos t]# java Ta22
复制代码


当我们输入 a 的时候打印 2,但是我们上线以后才发现,我们需要输出的 1,这个是如果要从本地更改要重新发布上线,为了这一个修改,明显是不值当的,但是如果我们用 redafine 热部署就可以帮助我们直接替换,不用重新发布 jvm


然后我们将 T 这个程序挂靠到 Arthas 上面去



然后我们直接修改 TT.java 程序 vi TT.java,将里面打印 2 的值修改成 1


public class TT{        public void m(){            System.out.println(1);        }}
复制代码


然后编译执行 ```javac TT.java ````


在回到我们挂靠的 Arthas 上面执行 redefine /usr/local/mxn/fuccGc/t/TT.class


[arthas@398842]$ redefine /usr/local/mxn/fuccGc/t/TT.classredefine success, size: 1, classes:TT
复制代码


执行成功大家可以看到我们在没有重新启动的情况下成功替换了 class 文件


watch

watch:方法执行的数据观测,可以通过 watch 指令,来监控某个类,监控后,运行下你的功能,复现下场景,arthas 会提供给你具体的出参和入参,帮助你排查故障

trace

输出方法调用路径,并输出耗时,这个指令对于优化代码非常的有用,可以看出具体每个方法执行的时间,如果是 for 循环等重复语句,还能看出 n 次循环中的最大耗时,最小耗时,和平均耗时,完美!

tt

在我们对某个方法开启 tt 后,会记录每一次调用(我们可以设置最大监控次数)的入参和返回参数,并能对这些不同时间下调进行观测[arthas@405136]$ tt -t FullGCTest modelFit



命令参数解析-ttt 命令有很多个主参数,-t 就是其中之一。这个参数的表明希望记录下类 *Test 的 print 方法的每次执行情况。-n 3 当你执行一个调用量不高的方法时可能你还能有足够的时间用 CTRL+C 中断 tt 命令记录的过程,但如果遇到调用量非常大的方法,瞬间就能将你的 JVM 内存撑爆。


此时你可以通过 -n 参数指定你需要记录的次数,当达到记录次数时 Arthas 会主动中断 tt 命令的记录过程,避免人工操作无法停止的情况。

ognl 表达式

ognl 表达式


OGNL 特殊用法请参考:https://github.com/alibaba/arthas/issues/71OGNL 表达式官方指南: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 有着比较全面的功能,上手也比较容易,对于刚开始入门的小伙伴也是可以轻松掌握的,对于文中有不懂或者有问题的小伙伴,大家可以在下面留言评论。


原创不易,希望大家多多捧场,记得一键三连!!!


我是牧小农,怕什么真理无穷,进一步有进一步的欢喜,大家加油!

发布于: 2021 年 05 月 13 日阅读数: 468
用户头像

牧小农

关注

业精于勤,荒于嬉;行成于思,毁于随! 2019.02.13 加入

业精于勤荒于嬉,行成于思毁于随

评论

发布
暂无评论
【死磕JVM】用Arthas排查JVM内存 真爽!我从小用到大