龙空技术网

Java 内存分析工具 Arthas 介绍与示例讲解

大数据老司机 2013

前言:

眼前你们对“java class分析工具”大概比较讲究,朋友们都需要分析一些“java class分析工具”的相关文章。那么小编也在网上收集了一些对于“java class分析工具””的相关资讯,希望我们能喜欢,姐妹们快快来了解一下吧!

一、概述

Arthas(阿尔萨斯)是阿里巴巴开源的一款Java诊断工具,用于实时检测、诊断Java应用程序的性能问题。它是一个命令行工具,提供了丰富的功能,包括查看类加载信息、方法执行耗时、线程堆栈、内存分析等。Arthas 的设计目标是在生产环境中实时诊断和解决Java应用程序的问题。

以下是 Arthas 的一些主要特点和功能:

实时性Arthas 可以在运行中的 Java 进程中实时进行诊断,无需重新启动应用。丰富的命令: 提供了众多的命令,涵盖了类加载、方法执行、线程、内存、GC 等多个方面。动态追踪: 支持实时动态追踪方法调用、线程堆栈等信息,方便定位问题。内存分析: 提供了 Heap DumpHistogramClassloader Stats 等命令,帮助进行内存分析。多种环境支持: 支持 LinuxMacWindows 操作系统,支持 HotSpotOpenJ9 JVM在线帮助: 提供了丰富的在线帮助,用户可以通过 help 命令查看每个命令的详细说明。

请注意,Arthas 的使用可能需要一些对 Java 应用程序的基本了解。在生产环境中使用 Arthas 时,需要谨慎操作,以免对应用程序产生影响。详细的使用方法和命令说明可以参考 Arthas 的官方文档:

二、Arthas 安装

全量安装

wget  -O arthas-packaging-3.7.1-bin.zipunzip arthas-packaging-3.7.1-bin.zip
三、Arthas 主要组成结构

主要有以下几大组件:

arthas-core.jar 是服务器端的启动入口类,调用 VirtualMachine#attach 到目标进程,并加载 arthas-agent.jar 作为 agent 包。arthas-agent.jar 既可以使用 premain 方式(在目标进程启动之前,通过-agent参数静态指定),也可以通过 agentmain 方式(在进程启动之后attach上去)。arthas-agent会使用自定义的classloader(ArthasClassLoader)加载arthas-core.jar里面的Configure类以及ArthasBootstrap。 同时程序运行的时候会使用arthas-spy.jararthas-spy.jar 里面只包含Spy类,目的是为了将Spy类使用BootstrapClassLoader来加载,从而使目标进程的java应用可以访问Spy类。通过ASM修改字节码,可以将Spy类的方法ON_BEFORE_METHODON_RETURN_METHOD等编织到目标类里面。arthas-client.jar 是客户端程序,用来连接arthas-core.jar启动的服务端代码,使用telnet方式。一般由arthas-boot.jaras.sh来负责启动。四、Arthas 通信主要流程

Arthas 应用是基于C/S的通信架构来设计的,支持 TelnetHttp 的客户端协议通信。

在服务器端的启动过程中会调用 ArthasBootstrap#bind 中, 会启动 TelnetHttp 通信协议的服务器实例并接收请求。服务器端接收到客户端连接后都会为每个连接生成会话窗口,并会将发过来的请求内容解析后生成命令交由任务控制去完成响应。其中每个客户端通信都对应唯一的 ShellImpl 实现,里面包括了唯一的Session 实例,并持有JobControllerImplInternalCommandManager 对象用于组装出异步任务去执行这个命令。InternalCommandManager 类记录了所有命令,通过名字可搜索到对应的命令实现类,这里Command类会被包装 AnnotatedCommand 类放入列表中。五、Arthas 快速入门讲解1)启动 Arthas

# 第一重方式./as.sh# 第二种方式(推荐)java -jar arthas-boot.jar
2)基础命令介绍
# 启动服务java -jar arthas-boot.jar# 查看帮助help 
base64 :base64 编码转换,和 linux 里的 base64 命令类似cat :打印文件内容,和 linux 里的 cat 命令类似cls:清空当前屏幕区域echo:打印参数,和 linux 里的 echo 命令类似grep:匹配查找,和 linux 里的 grep 命令类似help:查看命令帮助信息history:打印命令历史keymap:Arthas 快捷键列表及自定义快捷键pwd:返回当前的工作目录,和 linux 命令类似quit:退出当前 Arthas 客户端,其他 Arthas 客户端不受影响reset:重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端关闭时会重置所有增强过的类session:查看当前会话的信息stop:关闭 Arthas 服务端,所有 Arthas 客户端全部退出tee: 复制标准输入到标准输出和指定的文件,和 linux 里的 tee 命令类似version:输出当前目标 Java 进程所加载的 Arthas 版本号3)jvm 相关
# 启动服务java -jar arthas-boot.jar# 查看帮助help 
dashboard:当前系统的实时数据面板getstatic:查看类的静态属性heapdump:dump java heap, 类似 jmap 命令的 heap dump 功能jvm:查看当前 JVM 的信息logger:查看和修改 loggermbean:查看 Mbean 的信息memory:查看 JVM 的内存信息ognl:执行 ognl 表达式perfcounter:查看当前 JVM 的 Perf Counter 信息sysenv:查看 JVM 的环境变量sysprop:查看和修改 JVM 的系统属性thread:查看当前 JVM 的线程堆栈信息vmoption:查看和修改 JVM 里诊断相关的 optionvmtool:从 jvm 里查询对象,执行 forceGc1、dashboard(实时数据面板)
dashboard

数据说明:

ID:Java级别的线程ID,注意这个ID不能跟jstack中的nativeID一一对应NAME:线程名GROUP:线程组名PRIORITY:线程优先级, 1~10之间的数字,越大表示优先级越高STATE:线程的状态CPU%:线程消耗的cpu占比,采样100ms,将所有线程在这100ms内的cpu使用量求和,再算出每个线程的cpu使用占比。TIME:线程运行总时间,数据格式为分:秒INTERRUPTED:线程当前的中断位状态DAEMON:是否是daemon线程

GC区域说明:

gc.ps_scavenge.count:从应用程序启动到当前采样时间年轻代gc次数gc.ps_scavenge.time(ms):从应用程序启动到当前采样时间年轻代gc所用的总时间(毫秒)gc.ps_marksweep.count:从应用程序启动到当前采样时间老年代gc次数gc.ps_marksweep.time(ms):从应用程序启动到当前采样时间老年代gc所用的总时间(毫秒)

Memory 区域主要参数说明:

heap:堆内存使用情况(ps_eden_space+ps_survivor_space+ps_old_gen)ps_eden_space:伊甸园区内存使用情况ps_survivor_space:幸存区内存使用情况ps_old_gen :老年代内存使用情况nonheap:非堆内存使用情况

输入 q 或者 Ctrl+C 可以退出dashboard命令

2、Thread(线程相关堆栈信息)

参数说明:

数字:线程id[n:]:指定最忙的前N个线程并打印堆栈[b]:找出当前阻塞其他线程的线程[i ] :指定cpu占比统计的采样间隔,单位为毫秒

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

死锁线程查看

thread		# 查看线程状态thread -b	#	查看阻塞的线程信息
3、jvm(查看当前 JVM 的信息)
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、memory(查看 JVM 的内存信息)

memory
5、sysprop(查看/修改属性)
sysprop						#	查看所有属性sysprop java.version		#	查看单个属性,支持通过tab补全

修改某个属性

sysprop user.countryuser.country=US
6、sysenv(查看当前JVM的环境属性)
# 查看所有环境变量sysenv# 查看单个环境变量sysenv USER
7、vmoption(查看JVM中选项)
# 查看所有的选项vmoption# 查看指定的选项vmoption PrintGCDetails# 更新指定的选项vmoption PrintGCDetails true
8、getstatic(获取静态成员变量)
#	语法getstatic 类名 属性名# 显示demo.MathGame类中静态属性randomgetstatic demo.MathGame random
9、ognl(执行ognl表达式)

执行ognl表达式,这是从3.0.5版本新增的功能。

参数说明:

express:执行的表达式[c:]:执行表达式的 ClassLoader 的 hashcode,默认值是SystemClassLoader[x]:结果对象的展开层次,默认值1

举例:

调用静态函数

#	获取系统变量中值,并且打印(只会打印有返回值函数)ognl '@java.lang.System@out.println("hello")'

获取静态类的静态字段

#	获取代码中的运行返回值ognl '@demo.MathGame@random'

执行多行表达式,赋值给临时变量,返回一个List

#	计算value1、value2值,并存在List集合中ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
4)类和类加载器(class/classLoader)classloader:查看 classloader 的继承树,urls,类加载信息,使用 - classloader:去 getResourcedump:dump 已加载类的 byte code 到特定目录jad:反编译指定已加载类的源码mc:内存编译器,内存编译.java文件为.class文件redefine:加载外部的.class文件,redefine 到 JVM 里retransform:加载外部的.class文件,retransform 到 JVM 里sc:查看 JVM 已加载的类信息sm:查看已加载类的方法信息1、sc(查看类信息)

查看类的信息(sc: Search Class)

查看JVM已加载的类信息,“Search-Class” 的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息sc 默认开启了子类匹配功能,也就是说所有当前类的子类也会被搜索出来,想要精确的匹配,请打开options disable-sub-class true开关。

参数说明:

class-pattern:类名表达式匹配,支持全限定名,如com.taobao.test.AAA,也支持com/taobao/test/AAA这样的格式,这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把/替换为.啦。method-pattern:方法名表达式匹配[d]:输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的ClassLoader等详细信息。 如果一个类被多个ClassLoader所加载,则会出现多次[E]:开启正则表达式匹配,默认为通配符匹配[f]:输出当前类的成员变量信息(需要配合参数-d一起使用)

#	模糊搜索,demo包下所有的类sc demo.*#	打印类的详细信息sc -d demo.MathGame
2、sm(查看已加载方法信息)

查看已加载方法信息(“Search-Method” )

查看已加载类的方法信息“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。sm 命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。

参数说明:

class-pattern:类名表达式匹配method-pattern:方法名表达式匹配[d]:展示每个方法的详细信息[E]:开启正则表达式匹配,默认为通配符匹配

#	显示String类加载的方法sm java.lang.String# 	查看方法信息sm demo.MathGame# 	查看方法信息(详细信息-d)sm -d demo.MathGame
3、编译与反编译jad、mc、redefinejad:反编译字节码文件得到java的源代码mc:在内存中将源代码编译成字节码redefine:将字节码文件重新加载到内存中执行

【示例】jad 反编译已加载类源码

#	反编译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

【示例】mc 编译Java代码

#	在内存中编译Hello.java为Hello.classmc /root/Hello.java#	可以通过-d命令指定输出目录mc -d /root/bbb /root/Hello.java

【示例】redefine 加载外部.class文件

redefine的限制

不允许新增加field/method正在跑的函数,没有退出不能生效,比如下面新增加的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相应的目录下。

参数说明:

class-pattern:类名表达式匹配[c:]:类所属 ClassLoader 的 hashcode[E]:开启正则表达式匹配,默认为通配符匹配

#	把String类的字节码文件保存到~/logs/arthas/classdump/目录下dump java.lang.String#	把demo包下所有的类的字节码文件保存到~/logs/arthas/classdump/目录下dump demo.*
5、classloader(获取类加载器的信息)

作用:

classloader 命令将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls等。可以让指定的 classloadergetResources,打印出所有查找到的resourcesurl。对于ResourceNotFoundException异常比较有用。

参数说明:

[l]:按类加载实例进行统计[t]:打印所有ClassLoader的继承树[a]:列出所有ClassLoader加载的类,请谨慎使用[c:]:ClassLoader的hashcode[c: r:]:用ClassLoader去查找resource[c: load:]:用ClassLoader去加载指定的类

#	默认按类加载器的类型查看统计信息classloader#	按类加载器的实例查看统计信息,可以看到类加载的hashCodeclassloader -l#	查看ClassLoader的继承树classloader -t#	通过类加载器的hash,查看此类加载器实际所在的位置classloader -c 680f2737#	使用ClassLoader去查找指定资源resource所在的位置classloader -c 680f2737 -r META-INF/MANIFEST.MF#	使用ClassLoader去加载类classloader -c 70dea4e --load java.lang.String

classloader命令主要作用有哪些?

显示所有类加载器的信息获取某个类加载器所在的jar包获取某个资源在哪个jar包中加载某个类5)monitor/watch/trace 相关(核心监视功能)monitor - 方法执行监控stack - 输出当前方法被调用的调用路径trace - 方法内部调用路径,并输出方法路径上的每个节点上耗时tt - 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测watch - 方法执行数据观测1、monitor(监控方法的执行情况)

监控指定类中方法的执行情况用来监视一个时间段中指定方法的执行次数,成功次数,失败次数,耗时等这些信息。

参数说明:

class-pattern:类名表达式匹配method-pattern:方法名表达式匹配[E]:开启正则表达式匹配,默认为通配符匹配[c:]:统计周期,默认值为120秒

监控demo.MathGame类,并且每5S更新一次状态。

monitor demo.MathGame primeFactors -c 5

监控的维度说明:

timestamp:时间戳class:Java类method:方法(构造方法、普通方法)total:调用次数success:成功次数fail:失败次数rt:平均耗时fail-rate:失败率2、watch(检测函数返回值)

方法执行数据观测,让你能方便的观察到指定方法的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写OGNL 表达式进行对应变量的查看

参数说明:

class-pattern:类名表达式匹配method-pattern 方法名表达式匹配express 观察表达式condition-express 条件表达式[b]:在方法调用之前观察before[e]:在方法异常之后观察 exception[s]:在方法返回之后观察 success[f]:在方法结束之后(正常返回和异常返回)观察 finish[E]:开启正则表达式匹配,默认为通配符匹配[x:]:指定输出结果的属性遍历深度,默认为 1

这里重点要说明的是观察表达式,观察表达式的构成主要由ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。

特别说明:

watch 命令定义了4个观察事件点,即 -b 方法调用前,-e 方法异常后,-s 方法返回后,-f 方法结束后4个观察事件点-b、-e、-s 默认关闭,-f 默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出这里要注意方法入参和方法出参的区别,有可能在中间被修改导致前后不一致,除了 -b 事件点 params 代表方法入参外,其余事件都代表方法出参当使用 -b 时,由于观察事件点是在方法调用前,此时返回值或异常均不存在

#	查看方法执行的返回值watch demo.MathGame primeFactors returnObj#	观察demo.MathGame类中primeFactors方法出参和返回值,结果属性遍历深度为2。#	params:表示所有参数数组(因为不确定是几个参数)。#	returnObject:表示返回值watch demo.MathGame primeFactors "{params,returnObj}" -x 2# 查看执行前参数:# -b 方法执行前的参数watch demo.MathGame primeFactors "{params,returnObj}" -x 2 -b#	查看方法中的属性watch demo.MathGame primeFactors "{target}" -x 2 -b# 查看某一属性的值watch demo.MathGame primeFactors "{target.illegalArgumentCount}" -x 2 -b# 检测方法在执行前-b、执行后-s的入参params、属性target和返回值returnObjwatch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2# 输入参数小于0的情况:watch demo.MathGame primeFactors "{params[0],target}" "params[0]<0"
3、trace(根据路径追踪,并记录消耗时间)

对方法内部调用路径进行追踪,并输出方法路径上的每个节点上耗时。

trace 命令能主动搜索 class-pattern/method-pattern 对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。观察表达式的构成主要由ognl 表达式组成,所以你可以这样写"{params,returnObj}",只要是一个合法的 ognl 表达式,都能被正常支持。很多时候我们只想看到某个方法的rt大于某个时间之后的trace结果,现在Arthas可以按照方法执行的耗时来进行过滤了,例如trace *StringUtils isBlank '#cost>100'表示当执行时间超过100ms的时候,才会输出trace的结果。watch/stack/trace 这个三个命令都支持#cost耗时条件过滤。

参数说明:

class-pattern:类名表达匹配method-pattern:方法名表达式匹配condition-express:条件表达式,使用OGNL表达式[E]:开启正则表达式匹配,默认是通配符匹配[n:]:设置命令执行次数#cost:方法执行耗时,单位是毫秒

#	trace函数指定类的指定方法trace demo.MathGame run#	执行1次后退出trace demo.MathGame run -n 1#	默认情况下,trace不会包含jdk里的函数调用,如果希望trace jdk里的函数。#	需要显式设置--skipJDKMethod false。trace --skipJDKMethod false demo.MathGame run#	据调用耗时过滤,trace大于0.5ms的调用路径trace demo.MathGame run '#cost > .5'#	可以用正则表匹配路径上的多个类和函数,一定程度上达到多层trace的效果。trace -E com.test.ClassA|org.test.ClassB method1|method2|method3
4、stack(输出当前方法被调用的调用路径)

输出当前方法被调用的调用路径很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。

参数说明:

class-pattern:类名表达式匹配

method-pattern:方法名表达式匹配

condition-express:条件表达式,OGNL

[E]:开启正则表达式匹配,默认为通配符匹配

[n:]:执行次数限制

#	获取primeFactors的调用路径stack demo.MathGame primeFactors#	条件表达式来过滤,第0个参数的值小于0,-n表示获取2次stack demo.MathGame primeFactors 'params[0]<0' -n 2#	据执行时间来过滤,耗时大于0.5毫秒stack demo.MathGame primeFactors '#cost>0.5'
5、tt(时间隧道,记录多个请求)

time-tunnel 时间隧道。记录下指定方法每次调用的入参和返回信息,并能对这些不同时间下调用的信息进行观测。

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。于是乎,TimeTunnel 命令就诞生了。作用:记录指定方法每次调用的入参和返回值,并后期还可以对这些信息进行观测。

参数说明:

-t:记录某个方法在一个时间段中的调用-l:显示所有已经记录的列表-n:次数 只记录多少次-s:表达式 搜索表达式-i:索引号 查看指定索引号的详细调用信息-p:重新调用:指定的索引号时间碎片

#	最基本的使用来说,就是记录下当前方法的每次调用环境现场。tt -t demo.MathGame primeFactors

字段说明:

INDEX 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。TIMESTAMP 方法执行的本机时间,记录了这个时间片段所发生的本机时间COST(ms) 方法执行的耗时IS-RET 方法是否以正常返回的形式结束IS-EXP 方法是否以抛异常的形式结束OBJECT 执行对象的hashCode(),注意,曾经有人误认为是对象在 JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体CLASS 执行的类名METHOD 执行的方法名

#	对现有记录进行检索tt -l#	需要筛选出 `primeFactors` 方法的调用信息tt -s 'method.name=="primeFactors"'#	查看某条记录详细信息tt -i 1002
6)profiler 火焰图

profiler 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。

常用命令:

profiler

命令作用

profiler start

启动profiler,默认情况下,生成cpu的火焰图

profiler list

显示所有支持的事件

profiler getSamples

获取已采集的sample的数量

profiler status

查看profiler的状态,运行的时间

profiler stop

停止profiler,生成火焰图的结果,指定输出目录和输出格式:svg或html

启动profiler

profiler start

默认情况下,生成的是cpu的火焰图,即event为cpu。可以用--event参数来指定。

显示支持的事件

profiler list

获取已采集的sample的数量

profiler getSamples

查看profiler状态(可以查看当前profiler在采样哪种event和采样时间。)

profiler status

停止profiler,并同步生成文件(默认在工作目录下的arthas-output目录。)

[arthas@99095]$ profiler stopOKprofiler output file: /opt/arthas/arthas-output/20231203-171828.html

通过 --file 参数来指定输出结果路径

# 指定生成的文件名以及路径profiler stop --file /tmp/result.svg

可以用--format 指定生成格式

profiler stop --format html

生成的图(把输出的文件用浏览器打开):

火焰图的含义:火焰图是基于 perf 结果产生的SVG 图片,用来展示 CPU 的调用栈。

y 轴表示调用栈,每一层都是一个函数。调用栈越深,火焰就越高,顶部就是正在执行的函数,下方都是它的父函数。x 轴表示抽样数,如果一个函数在 x 轴占据的宽度越宽,就表示它被抽到的次数多,即执行的时间长。注意,x 轴不代表时间,而是所有的调用栈合并后,按字母顺序排列的。火焰图就是看顶层的哪个函数占据的宽度最大。只要有"平顶"(plateaus),就表示该函数可能存在性能问题。颜色没有特殊含义,因为火焰图表示的是 CPU 的繁忙程度,所以一般选择暖色调。

Java 内存分析工具 Arthas 介绍与示例讲解就先到这里了,有任何疑问也可关注我公众号:大数据与云原生技术分享,进行技术交流,如本篇文章对您有所帮助,麻烦帮忙一键三连(点赞、转发、收藏)~

标签: #java class分析工具