【JVM】Arthas相关分析命令
【JVM】Arthas相关分析命令
- 【一】下载和启动
- 【二】JVM相关的核心功能
- (1)类别 1:JVM基础信息监控
- (2)类别 2:类与类加载器信息
- (3)类别 3:性能分析与火焰图
- 【三】JVM相关的命令
- 【1】JVM相关命令
- (1)dashboard:实时数据面板
- (2)Thread:线程相关堆栈信息
- (3)jvm
- (4)sysprop:查看/修改属性
- (5)sysenv:查看JVM环境属性
- (6)vmpotion:查看JVM中选项
- (7)getstatic:获取静态成员变量
- (8)ognl:执行ognl表达式
- 【2】类和类加载器(class/classLoader)
- (1)sc:查看类信息
- (2)sm:查看已加载方法信息
- (3)编译与反编译jad、mc、redefine
- (4)dump:保存已加载字节码文件到本地
- (5)classloader:获取类加载器的信息
- 【四】JVM分析案例
- (1)准备测试的案例代码(死锁、内存泄漏多种情况、死循环)
- (2)运行代码,启动arthas
- (3)排查定位CPU飙高问题(死循环)
- (4)排查定位死锁问题(死锁)
【一】下载和启动
下载地址:https://arthas.aliyun.com/doc/download.html
直接下载全量包
Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:
curl-L https://arthas.aliyun.com/install.sh|sh(1)用 as.sh 启动
解压后,在文件夹里有as.sh,直接用./as.sh的方式启动:
./as.sh打印帮助信息:
./as.sh-h(2)用 arthas-boot 启动
或者在解压后,在文件夹里有arthas-boot.jar,直接用java -jar的方式启动:
java-jar arthas-boot.jar打印帮助信息:
java-jar arthas-boot.jar-h(3)注意
在Linux上启动arthas的时候,要注意是不是root权限,并且注意项目启动的用户和当前启动arthas的用户是不是相同
【二】JVM相关的核心功能
(1)类别 1:JVM基础信息监控
(1)dashboard: 核心命令。提供一个实时更新的仪表盘,一站式查看:
1-线程信息(显示CPU占用高的线程)
2-内存信息(堆内存、元空间等使用情况)
3-垃圾回收(GC)次数和时间
4-主机信息(CPU、负载)
5-运行时信息
6-作用:快速对应用的健康状况有一个整体了解,快速发现资源瓶颈。
(2)jvm: 查看详细的JVM信息,包括线程、内存、GC、加载类、操作系统等信息。
作用:深入了解JVM的详细配置和状态。
(3)thread: 查看所有线程的信息,以及单个线程的堆栈。
1-thread : 查看指定线程的堆栈,非常适合定位CPU飙升问题。
2-thread -b: 直接找出阻塞其他线程的根因线程(查找死锁)。
3-作用:诊断线程相关问题,如CPU占用高、死锁、响应慢。
(4)sysenv/ sysprop: 查看系统的环境变量和JVM的系统属性。
作用:确认应用运行时读取的配置是否正确,例如Spring的active profile、数据库连接地址等。
(2)类别 2:类与类加载器信息
(1)sc(Search Class): 查看已加载的类信息,包括来源哪个Jar包。
1-sc -d com.example.MyClass: 显示类的详细信息,包括其类加载器。
2-作用:排查类冲突(如NoSuchMethodError, NoClassDefFoundError),确认加载的类版本是否正确。
(2)sm(Search Method): 查看类的方法信息。
1-sm com.example.MyClass *Method: 列出类中所有的方法。
2-作用:确认方法是否存在,以及它的描述符。
(3)classloader: 查看类加载器的继承树、加载了哪些类、是否存在重复的Jar包或类。
1-作用:深入诊断类加载冲突、内存泄漏(类加载器无法被GC会导致其加载的所有类都无法卸载)。
(3)类别 3:性能分析与火焰图
(1)profiler: 生成火焰图(Flame Graph),一种非常直观的性能分析图表。
1-profiler start: 开始采样。
2-profiler stop: 停止采样并生成火焰图文件。
3-作用:从宏观上定位CPU占用热点,直观地看到代码的“火”(最耗CPU)在哪里。
【三】JVM相关的命令
【1】JVM相关命令
(1)dashboard:实时数据面板
查看当前系统的实时数据面板。
dashboard输入 q 或者 Ctrl+C 可以退出dashboard命令数据说明:
ID:Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应 NAME:线程名GROUP:线程组名 PRIORITY:线程优先级,1~10之间的数字,越大表示优先级越高 STATE:线程的状态 CPU%:线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。 TIME:线程运行总时间,数据格式为分:秒 INTERRUPTED:线程当前的中断位状态 DAEMON:是否是daemon线程(2)Thread:线程相关堆栈信息
线程相关堆栈信息。
(1)Arthas支持管道,可以用 thread 1 | grep ‘main(’ 查找到main class。
thread 1|grep'main('thread# 显示所有线程的信息thread 1# 显示1号线程的运行堆栈thread-b# 查看阻塞的线程信息thread-n 3# 查看最忙的3个线程,并打印堆栈thread-i 1000-n 3# 指定采样时间间隔,每过1000毫秒采样,显示最占时间的3个线程查看处于等待状态的线程(WAITING、BLOCKED) thread--state WAITING(2)死锁线程查看
thread# 查看线程状态thread-b# 查看阻塞的线程信息(3)jvm
THREAD相关
COUNT:JVM当前活跃的线程数 DAEMON-COUNT: JVM当前活跃的守护线程数 PEAK-COUNT:从JVM启动开始曾经活着的最大线程数 STARTED-COUNT:从JVM启动开始总共启动过的线程次数 DEADLOCK-COUNT:JVM当前死锁的线程数文件描述符相关
MAX-FILE-DESCRIPTOR-COUNT:JVM进程最大可以打开的文件描述符数OPEN-FILE-DESCRIPTOR-COUNT:JVM当前打开的文件描述符数(4)sysprop:查看/修改属性
sysprop# 查看所有属性sysprop java.version# 查看单个属性,支持通过tab补全修改某个属性
sysprop user.country user.country=US(5)sysenv:查看JVM环境属性
查看当前JVM的环境属性(System Environment Variables)
# 查看所有环境变量sysenv# 查看单个环境变量sysenv USER(6)vmpotion:查看JVM中选项
查看JVM中选项,可以修改
# 查看所有的选项vmoption# 查看指定的选项vmoption PrintGCDetails# 更新指定的选项vmoption PrintGCDetails true更新某一个值
(7)getstatic:获取静态成员变量
获取静态成员变量
# 语法getstatic 类名 属性名# 显示demo.MathGame类中静态属性randomgetstatic demo.MathGame random(8)ognl:执行ognl表达式
执行ognl表达式,这是从3.0.5版本新增的功能。
(1)调用静态函数
# 获取系统变量中值,并且打印(只会打印有返回值函数)ognl'@java.lang.System@out.println("hello")'(2)获取静态类的静态字段
# 获取代码中的运行返回值ognl'@demo.MathGame@random'(3)执行多行表达式,赋值给临时变量,返回一个List
# 计算value1、value2值,并存在List集合中ognl'#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'【2】类和类加载器(class/classLoader)
(1)sc:查看类信息
查看类的信息(sc: Search Class)
查看JVM已加载的类信息,“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息
sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关。
# 模糊搜索,demo包下所有的类scdemo.*# 打印类的详细信息sc-d demo.MathGame(2)sm:查看已加载方法信息
查看已加载方法信息(“Search-Method” )
查看已加载类的方法信息“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
# 显示String类加载的方法sm java.lang.String# 查看方法信息sm demo.MathGame# 查看方法信息(详细信息-d)sm-d demo.MathGame(3)编译与反编译jad、mc、redefine
(1)jad:反编译已加载类源码
反编译指定已加载类源码,jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便。当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解。
# 反编译MathGame方法jad demo.MathGame# 反编绎时只显示源代码(排除ClassLoader信息)。# 默认情况下,反编译结果里会带有ClassLoader信息,通过--source-only选项,可以只打印源代码。方便和mc/redefine命令结合使用。jad--source-only demo.MathGame# 反编译到指定文件中jad--source-only demo.MathGame > Hello.java# 只反编译mathGame类型中main方法jad demo.MathGame main
(2)mc:编译Java代码
内存编译,Memory Compiler/内存编译器,编译.java文件生成.class
# 在内存中编译Hello.java为Hello.classmc/root/Hello.java# 可以通过-d命令指定输出目录mc-d/root/bbb/root/Hello.java
(3)redefine:加载外部.class文件
加载外部的.class文件,redefine到JVM里。
注意, redefine后的原来的类不能恢复,redefine有可能失败(比如增加了新的field)。
reset命令对redefine的类无效。如果想重置,需要redefine原始的字节码。
redefine命令和jad/watch/trace/monitor/tt等命令会冲突。执行完redefine之后,如果再执行上面提到的命令,则会把redefine的字节码重置。
redefine的限制
(1)不允许新增加field/method
(2)正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效。
# 1. 使用jad反编译demo.MathGame输出到/root/MathGame.javajad--source-only demo.MathGame >/root/MathGame.java# 2.按上面的代码编辑完毕以后,使用mc内存中对新的代码编译mc/root/MathGame.java-d/root# 3.使用redefine命令加载新的字节码redefine/root/demo/MathGame.class(4)dump:保存已加载字节码文件到本地
将已加载类的字节码文件保存在特定目录:logs/arthas/classdump。不同的类加载器放在不同的目录下。dump作用:将正在JVM中运行的程序的字节码文件提取出来,保存在logs相应的目录下
# 把String类的字节码文件保存到~/logs/arthas/classdump/目录下dump java.lang.String# 把demo包下所有的类的字节码文件保存到~/logs/arthas/classdump/目录下dump demo.*(5)classloader:获取类加载器的信息
获取类加载器的信息
作用:
(1)classloader 命令将 JVM 中所有的classloader的信息统计出来,并可以展示继承树,urls等。
(2)可以让指定的classloader去getResources,打印出所有查找到的resources的url。对于ResourceNotFoundException异常比较有用。
# 默认按类加载器的类型查看统计信息classloader# 按类加载器的实例查看统计信息,可以看到类加载的hashCodeclassloader-l# 查看ClassLoader的继承树classloader-t# 通过类加载器的hash,查看此类加载器实际所在的位置classloader-c 680f2737# 使用ClassLoader去查找指定资源resource所在的位置classloader-c 680f2737-r META-INF/MANIFEST.MF# 使用ClassLoader去查找类的class文件所在的位置classloader-c 680f2737-r java/lang/String.class# 使用ClassLoader去加载类classloader-c 70dea4e--load java.lang.Stringclassloader命令主要作用有哪些?
(1)显示所有类加载器的信息
(2)获取某个类加载器所在的jar包
(3)获取某个资源在哪个jar包中
(4)加载某个类
【四】JVM分析案例
(1)准备测试的案例代码(死锁、内存泄漏多种情况、死循环)
publicclassjvmUpdate02{privatestaticHashSethashSet=newHashSet();publicstaticvoidmain(String[]args){//模拟死锁deadThread();//创建对象addHashSetThread();//模拟高CPUcpuHigh();}/** 模拟死锁 * @MethodName: deadThread * @Author: AllenSun * @Date: 2022/11/9 上午12:59 */privatestaticvoiddeadThread(){Objectlock1=newObject();Objectlock2=newObject();newThread(()->{synchronized(lock1){try{System.out.println("线程1启动");Thread.sleep(5000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(lock2){System.out.println("线程1结束");}}}).start();newThread(()->{synchronized(lock2){try{System.out.println("线程2启动");Thread.sleep(5000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(lock1){System.out.println("线程2结束");}}}).start();System.out.println("主线程结束");}/**不断的向HashSet集合添加数据 * @MethodName: addHashSetThread * @Author: AllenSun * @Date: 2022/11/9 上午1:03 */publicstaticvoidaddHashSetThread(){newThread(()->{intcount=0;while(true){try{hashSet.add("count"+count);Thread.sleep(10000);count++;}catch(InterruptedExceptione){e.printStackTrace();}}}).start();}/** 模拟CPU飙升 * @MethodName: cpuHigh * @Author: AllenSun * @Date: 2022/11/9 上午1:05 */publicstaticvoidcpuHigh(){newThread(()->{while(true){}}).start();}}(2)运行代码,启动arthas
先运行代码,然后启动arthas:java -jar arthas-boot.jar
(3)排查定位CPU飙高问题(死循环)
(1)输入dashboard:仪表盘,内存使用情况综合界面
可以看出来下面线程id为14的线程占用的CPU较高,接下来对其进行分析
(2)thread 线程id:查看问题线程的具体情况
ctrl+c退出仪表盘
thread 14查看对应线程的情况
找到92行,果然看到方法体中有一个无限循环,导致了CPU飙高
(3)thread -n 1:列出CPU占用前1的线程
(4)排查定位死锁问题(死锁)
(1)thread -b:定位死锁问题出现的位置
定位到42行,这个lock2在下面被尝试获取失败导致产生的死锁
(2)thread,获取所有线程的情况
根据状态State的字段【BLOCKED】可以找到具体产生死锁的方法
