在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
Arthas原理和使用,大概说下吧?
Arthas 也是大家定位和解决线上问题,非常常用的一个工具。
所以,这个题目如果答不上来, 说明平时没怎么解决过线上问题, 面试基本就挂。
所以,这道题目,非常重要。
这里,尼恩把这道面试题以及参考答案,做了详细的梳理。 大家可以收藏起来, 时不时看看,做到温故而知新。
同时,也收入咱们的 《尼恩Java面试宝典PDF》V159版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】取
Arthas
是 Alibaba 开源的 Java 诊断工具,深受开发者喜爱。
通常,本地开发环境无法访问生产环境。如果在生产环境中遇到问题,则无法使用 IDE 远程调试。
更糟糕的是,在生产环境中调试是不可接受的,因为它会暂停所有线程,导致服务暂停。
Arthas 旨在解决这些问题。开发人员可以通过Arthas 在线解决生产问题。通过Arthas ,无需 JVM 重启,无需代码更改。 Arthas 作为观察者永远不会暂停正在运行的线程。
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
Arthas中集成了大部分JDK工具的功能实现,因此,在线上情况时,可以通过它快速的帮助我们解决问题,如CPU占用过高、线程阻塞、死锁、代码动态修改、方法执行缓慢、排查404
等。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
Arthas
支持JDK6+
,支持Linux/Mac/Winodws
,采用命令行交互模式,同时提供丰富的Tab
自动补全功能,进一步方便进行问题的定位和诊断。
阿里提供的在线的Arthas Terminal
学习方式(Arthas Tutorials (aliyun.com)),帮助大家快速上手。
推荐使用arthas-boot
(推荐),下载arthas-boot.jar
,然后用java -jar
的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
打印帮助信息:
java -jar arthas-boot.jar -h
arthas-boot启动过程中, 会下载一些依赖包,如果下载速度比较慢,可以使用aliyun的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
arthas-boot 完成依赖下载,并且启动。
启动之后,这时候,arthas会列出当前检测到的java进程,
Arthas
会将本机中所有的Java进程查询出来,类似于jps/ps
的作用:
上面的例子中, 有一个 java进程, Arthas 自己的进程
30162 Arthas.jar
如果你的机器中启动了多个Java应用,此时会查询出来一个应用列表。
要操作那个进程,我们可以根据前面的序号进行输入。
输入选择自己要操作的Java应用,如上情况中,再输入1
即可,并按回车键即可;
$ 1
最终,Arthas
成功启动,接下来再通过Arthas
提供的指令进行操作即可:
Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,有个专门的install.sh脚本
请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:
curl -L https://arthas.aliyun.com/install.sh | sh
上述命令会下载启动脚本文件 install.sh,并且下载 as.sh
到当前目录,你可以放在任何地方或将其加入到 $PATH
中。
直接在shell下面执行./as.sh
,就会进入交互界面。
也可以执行./as.sh -h
来获取更多参数信息。
可以通过下面的方式自己动手实践。
math-game是一个简单的程序,每隔一秒生成一个随机数,再执行质因数分解,并打印出分解结果。
math-game
源代码:查看
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
在命令行下面执行(使用和目标进程一致的用户启动,否则可能 attach 失败):
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
执行该程序的用户需要和目标进程具有相同的权限。
比如以admin
用户来执行:sudo su admin && java -jar arthas-boot.jar
或 sudo -u admin -EH java -jar arthas-boot.jar
。
如果 attach 不上目标进程,可以查看~/logs/arthas/
目录下的日志。
如果下载速度比较慢,可以使用 aliyun 的镜像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http
java -jar arthas-boot.jar -h
打印更多参数信息。
选择应用 java 进程:
$ $ java -jar arthas-boot.jar
* [1]: 35542
[2]: 71560 math-game.jar
math-game
进程是第 2 个,则输入 2,再输入回车/enter
。
Arthas 会 attach 到目标进程上,并输出日志:
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$
输入 dashboard,按回车/enter
,会展示当前进程的信息,按ctrl+c
可以中断执行。
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre
通过 thread 命令来获取到math-game
进程的 Main Class
thread 1
会打印线程 ID 1 的栈,通常是 main 函数的线程。
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)
得到了 Main Class 的全路径名称 demo.MathGame
jad 全路径名称, 进行源码的反编译
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/math-game.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {
try {
int number = random.nextInt();
List<Integer> primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
.......
return result;
}
public List<Integer> primeFactors(int number) {
if (number < 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
}
}
Affect(row-cnt:1) cost in 970 ms.
通过 watch命令来查看到指定函数的调用情况。
watch能观察到的范围为:返回值
、抛出异常
、入参
,通过编写 OGNL 表达式进行对应变量的查看。
例子,通过 watch命令来查看 demo.MathGame#primeFactors 函数的返回值:
$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]
更多的功能可以查看进阶教程在新窗口打开。
如果只是退出当前的连接,可以用quit
或者exit
命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
如果想完全退出 arthas,可以执行stop
命令。
Arthas
从最初的发布开始,随着后续社区的活跃性增强及用户群体的不断壮大,指令也越发完善与丰富,至目前为止提供了基础命令、JVM命令、class命令以及字节码增强命令等几大类。
sc “Search-Class” 的简写,查看JVM已加载的类信息,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息,
可选项如下:
class-pattern
:类名表达式匹配(必填),如sc java.lang.String
。-E
:开启正则表达式匹配,默认为通配符匹配。-c
:指定class
的类加载器的哈希码。-d
:显示当前类的详细信息,包含来源、声明、类加载相关等信息。-f
:输出当前类的属性成员信息,与-d
一同使用。-x
:指定输出静态变量时属性的遍历深度,默认为0
。-n
:具有详细信息的匹配类的最大数量(默认为100)。提示
class-pattern 支持全限定名,支持两种格式:
com.taobao.test.AAA,
也支持 com/taobao/test/AAA 这样的格式,
这样,我们从异常堆栈里面把类名拷贝过来的时候,不需要在手动把
/
替换为.
啦。
$ sc demo.*
demo.MathGame
Affect(row-cnt:1) cost in 55 ms.
$ sc -d demo.MathGame
class-info demo.MathGame
code-source /private/tmp/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
Affect(row-cnt:1) cost in 875 ms.
$ sc -d -f demo.MathGame
class-info demo.MathGame
code-source /private/tmp/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
fields modifierprivate,static
type java.util.Random
name random
value java.util.Random@522b4
08a
modifierprivate
type int
name illegalArgumentCount
Affect(row-cnt:1) cost in 19 ms.
“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm
命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
sm
:查看已加载类的方法信息,可选项如下:
class-pattern
:类名表达式匹配(必填),如sm java.lang.String
。-E
:开启正则表达式匹配,默认为通配符匹配。-d
:查看方法的详细信息,配合方法名使用,如sm -d java.lang.String toString
。-c
:同sc -c
作用相同。-n
:同sc -h
作用相同。参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 展示每个方法的详细信息 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 指定 class 的 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[n:] | 具有详细信息的匹配类的最大数量(默认为 100) |
$ sm java.lang.String
java.lang.String-><init>
java.lang.String->equals
java.lang.String->toString
java.lang.String->hashCode
java.lang.String->compareTo
java.lang.String->indexOf
java.lang.String->valueOf
.......
Affect(row-cnt:44) cost in 1342 ms.
$ sm -d java.lang.String toString
declaring-class java.lang.String
method-name toString
modifier public
annotation
parameters
return java.lang.String
exceptions
Affect(row-cnt:1) cost in 3 ms.
jad
命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑;如需批量下载指定包的目录的 class 字节码可以参考 dump。
在 Arthas Console 上,反编译出来的源码是带语法高亮的,阅读更方便
当然,反编译出来的 java 代码可能会存在语法错误,但不影响你进行阅读理解
jad
:反编译指定已加载类的源码,可选项如下:
-c、-E
都与前面的作用相同,举几个案例演示用法。jad --source-only java.lang.String
:只显示反编译后的Java源码。jad java.lang.String
:反编译指定类。jad java.lang.String toString
:反编译指定类的某个方法。参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] | 类所属 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
jad 全路径名称, 进行源码的反编译
前面有例子
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/math-game.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
...
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
.....
}
Affect(row-cnt:1) cost in 970 ms.
Memory Compiler/内存编译器,编译.java
文件生成.class
。
mc
:内存编译器,编译.java
源文件为.class
类文件,可选项如下:
-c
:指定类加载器(以哈希码的方式指定)。-d
:指定编译后的类文件输出位置。加载外部的.class
文件,retransform (再转化;插桩) jvm 已加载的类, 比如在线替换 类的方法。
retransform
:起到 热部署的作用,用于线上替换类方法。
注意点:
retransform 使用参考
retransform /tmp/Test.class
retransform -l
retransform -d 1 # delete retransform entry
retransform --deleteAll # delete all retransform entries
retransform --classPattern demo.* # triger retransform classes
retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class
dump`:导出已加载类的字节码数据到指定目录
dump 命令将 JVM 中实际运行的 class 的 byte code dump 到指定目录,适用场景批量下载指定包目录的 class 字节码;如需反编译单一类、实时查看类信息,可参考 jad。
dump命令可选项如下:
-c、-E
作用与之前的相同。-d
:指定输出的路径,如dump -d /usr/data/byteCode java.lang.String
。参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] | 类所属 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[d:] | 设置类文件的目标目录 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
$ dump java.lang.String
HASHCODE CLASSLOADER LOCATION
null /Users/admin/logs/arthas/classdump/java/lang/String.class
Affect(row-cnt:1) cost in 119 ms.
classloader
:查类加载器的继承树,urls,类加载信息,
classloader 命令将 JVM 中所有的 classloader 的信息统计出来,并可以展示继承树,urls 等。
可以让指定的 classloader 去 getResources,打印出所有查找到的 resources 的 url。对于ResourceNotFoundException比较有用。
classloader 可选项如下:
-a
:显示所有类加载器加载的所有类。-c
:查看指定的类加载器的加载路径,如classloader -c 14ae5a5
。-l
:统计每个类加载器的加载信息。-r
:查找某个的资源路径,配合-c
使用,如classloader -c 33909752 -r java/lang/String.class
。-t
:以树结构列出每个类加载器之间的父子关系。-u
:显示类加载器的url统计信息,如加载总数、父子关系、加载范围等。-i
:查看每种类加载器的实例数量及其加载总量。按类加载类型查看统计信息
$ classloader
name numberOfInstances loadedCountTotal
com.taobao.arthas.agent.ArthasClassloader 1 2115
BootstrapClassLoader 1 1861
sun.reflect.DelegatingClassLoader 5 5
sun.misc.Launcher$AppClassLoader 1 4
sun.misc.Launcher$ExtClassLoader 1 1
Affect(row-cnt:5) cost in 3 ms.
按类加载实例查看统计信息
$ classloader -l
name loadedCount hash parent
BootstrapClassLoader 1861 null null
com.taobao.arthas.agent.ArthasClassloader@68b31f0a 2115 68b31f0a sun.misc.Launcher$ExtClassLoader@66350f69
sun.misc.Launcher$AppClassLoader@3d4eac69 4 3d4eac69 sun.misc.Launcher$ExtClassLoader@66350f69
sun.misc.Launcher$ExtClassLoader@66350f69 1 66350f69 null
Affect(row-cnt:4) cost in 2 ms.
查看 ClassLoader 的继承树
$ classloader -t
+-BootstrapClassLoader
+-sun.misc.Launcher$ExtClassLoader@66350f69
+-com.taobao.arthas.agent.ArthasClassloader@68b31f0a
+-sun.misc.Launcher$AppClassLoader@3d4eac69
Affect(row-cnt:4) cost in 3 ms.
查看 URLClassLoader 实际的 urls
$ classloader -c 3d4eac69
file:/private/tmp/math-game.jar
file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar
Affect(row-cnt:9) cost in 3 ms.
注意 hashcode 是变化的,需要先查看当前的 ClassLoader 信息,提取对应 ClassLoader 的 hashcode。
对于只有唯一实例的 ClassLoader 可以通过 class name 指定,使用起来更加方便:
$ classloader --classLoaderClass sun.misc.Launcher$AppClassLoader
file:/private/tmp/math-game.jar
file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar
Affect(row-cnt:9) cost in 3 ms.
使用 ClassLoader 去查找 resource
$ classloader -c 3d4eac69 -r META-INF/MANIFEST.MF
jar:file:/System/Library/Java/Extensions/MRJToolkit.jar!/META-INF/MANIFEST.MF
jar:file:/private/tmp/math-game.jar!/META-INF/MANIFEST.MF
jar:file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar!/META-INF/MANIFEST.MF
也可以尝试查找类的 class 文件:
$ classloader -c 1b6d3586 -r java/lang/String.class
jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/String.class
使用 ClassLoader 去加载类
$ classloader -c 3d4eac69 --load demo.MathGame
load class success.
class-info demo.MathGame
code-source /private/tmp/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
dashboard:当前系统的实时数据面板,资源监控仪表盘,
dashboard 包含线程、内存、GC、运行环境等信息, 按 ctrl+c 退出。
dashboard
可选项如下:
-i
:刷新实时数据的间隔时间,默认为5000ms
。-n
:刷新实时数据的次数,默认为一直持续刷新,按ctrl+c
退出。参数名称 | 参数说明 |
---|---|
[i:] | 刷新实时数据的时间间隔 (ms),默认 5000ms |
[n:] | 刷新实时数据的次数 |
$ dashboard
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
-1 C2 CompilerThread0 - -1 - 1.55 0.077 0:8.684 false true
53 Timer-for-arthas-dashboard-07b system 5 RUNNABLE 0.08 0.004 0:0.004 false true
22 scheduling-1 main 5 TIMED_WAI 0.06 0.003 0:0.287 false false
-1 C1 CompilerThread0 - -1 - 0.06 0.003 0:2.171 false true
-1 VM Periodic Task Thread - -1 - 0.03 0.001 0:0.092 false true
49 arthas-NettyHttpTelnetBootstra system 5 RUNNABLE 0.02 0.001 0:0.156 false true
16 Catalina-utility-1 main 1 TIMED_WAI 0.0 0.000 0:0.029 false false
-1 G1 Young RemSet Sampling - -1 - 0.0 0.000 0:0.019 false true
17 Catalina-utility-2 main 1 WAITING 0.0 0.000 0:0.025 false false
34 http-nio-8080-ClientPoller main 5 RUNNABLE 0.0 0.000 0:0.016 false true
23 http-nio-8080-BlockPoller main 5 RUNNABLE 0.0 0.000 0:0.011 false true
-1 VM Thread - -1 - 0.0 0.000 0:0.032 false true
-1 Service Thread - -1 - 0.0 0.000 0:0.006 false true
-1 GC Thread#5 - -1 - 0.0 0.000 0:0.043 false true
Memory used total max usage GC
heap 36M 70M 4096M 0.90% gc.g1_young_generation.count 12
g1_eden_space 6M 18M -1 33.33% 86
g1_old_gen 30M 50M 4096M 0.74% gc.g1_old_generation.count 0
g1_survivor_space 491K 2048K -1 24.01% gc.g1_old_generation.time(ms) 0
nonheap 66M 69M -1 96.56%
codeheap_'non-nmethods' 1M 2M 5M 22.39%
metaspace 46M 47M -1 98.01%
Runtime
os.name Mac OS X
os.version 10.15.4
java.version 15
java.home /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home
systemload.average 10.68
processors 8
uptime 272s
秒
分:秒
Java 8 之后支持获取 JVM 内部线程 CPU 时间,这些线程只有名称和 CPU 时间,没有 ID 及状态等信息(显示 ID 为-1)。
JVM 内部线程包括下面几种:
C1 CompilerThread0
, C2 CompilerThread0
GC Thread0
, G1 Young RemSet Sampling
VM Periodic Task Thread
, VM Thread
, Service Thread
通过内部线程可以观测到 JVM 活动,如 GC、JIT 编译等占用 CPU 情况,方便了解 JVM 整体运行状况。
trace/watch/tt/redefine
等命令后,可以看到 JIT 线程活动变得更频繁。因为 JVM 热更新 class 字节码时, 清除了此 class 相关的 JIT 编译结果,需要重新编译。查看当前线程信息,查看线程的堆栈
thread
:查看当前线程的堆栈信息,可选项如下:
-n
:显示最活跃的n
条线程信息,如thread -n 5
。-i
:指定活跃性统计的采样间隔时间,如thread -i 5000
。-b
:自动检测出应用中当前阻塞其他线程的线程。--state
:查询目前程序中处于指定状态的线程,如thread --state BLOCKED
。id
:查看某个线程的详细信息,如thread 21
。参数列表
参数名称 | 参数说明 |
---|---|
id | 线程 id |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value> ] | 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[–all] | 显示所有匹配的线程 |
这里的 cpu 使用率与 linux 命令top -H -p <pid>
的线程%CPU
类似,一段采样间隔时间内,当前 JVM 里各个线程的增量 cpu 时间与采样间隔时间的比例。
工作原理说明:
java.lang.management.ThreadMXBean#getThreadCpuTime()
及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
接口)-i
指定间隔时间)注意
注意: 这个统计也会产生一定的开销(JDK 这个接口本身开销比较大),因此会看到 as 的线程占用一定的百分比,为了降低统计自身的开销带来的影响,可以把采样间隔拉长一些,比如 5000 毫秒。
$ thread -n 3
"C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms
"arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLE
at java.management@11.0.7/sun.management.ThreadImpl.dumpThreads0(Native Method)
at java.management@11.0.7/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
at java.base@11.0.7/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base@11.0.7/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base@11.0.7/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base@11.0.7/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base@11.0.7/java.lang.Thread.run(Thread.java:834)
"VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
[Internal]
表示为 JVM 内部线程,参考dashboard命令的介绍。cpuUsage
为采样间隔时间内线程的 CPU 使用率,与dashboard命令的数据一致。deltaTime
为采样间隔时间内线程的增量 CPU 时间,小于 1ms 时被取整显示为 0ms。time
线程运行总 CPU 时间。注意:线程栈为第二采样结束时获取,不能表明采样间隔时间内该线程都是在处理相同的任务。建议间隔时间不要太长,可能间隔时间越大越不准确。 可以根据具体情况尝试指定不同的间隔时间,观察输出结果。
显示所有匹配线程信息,有时需要获取全部 JVM 的线程数据进行分析
jvm
:查看JVM信息,包含线程/内存/OS/内存结构/编译/类加载/运行环境等信息。
使用参考,请参见 官方文档
查看当前 JVM 的系统属性(System Property
)
如sysprop java.home
。
使用参考,请参见 官方文档
sysenv
:,查看当前JVM的环境参数。
使用参考,请参见 官方文档
vmoption
:查看或修改JVM的运行时参数,如:
vmoption PrintGC
:查看PrintGC
是否开启。vmoption PrintGC true
:更改PrintGC
参数。使用参考,请参见 官方文档
vmoption | arthas (aliyun.com)
getstatic
:查看类的静态属性,用法:getstatic class_nmae field_name
。
使用参考,请参见 官方文档
getstatic | arthas (aliyun.com)
ognl
:执行ognl表达式
使用参考,请参见 官方文档
heapdump
:类似于jmap
工具的堆dump
功能,使用方式:
heapdump /usr/data/dump/heap.hprof
:导出堆快照到指定文件。heapdump --live /usr/data/dump/heap.hprof
:只导出存活对象的快照。使用参考,请参见 官方文档
heapdump | arthas (aliyun.com)
mbean
:查看Mbean
的信息
使用参考,请参见 官方文档
memory
:查看JVM的内存划分、内存结构以及占用率。
使用参考,请参见 官方文档
让你能方便的观察到指定函数的调用情况。能观察到的范围为:返回值
、抛出异常
、入参
,通过编写 OGNL 表达式进行对应变量的查看。
使用参考,请参见 官方文档
watch
:观测指定方法的执行情况,可选项如下:
-b
:在方法调用之前观测。-s
:在方法成功执行后观测。-e
:在方法异常执行后观测。-f
:在方法结束后进行观测(默认)。-n
:指定观测的次数。watch -s -n 10 demo.MathGame primeFactors
watch
虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。
这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。
于是乎,TimeTunnel 命令就诞生了。
tt
:
tt 全称 TimeTunnel ,意思是 : 方法执行数据的时空隧道,
记录指定方法每次执行的数据,并能在不同的时间下调用观测,
记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测
使用参考,请参见 官方文档
tt命令可选项如下:
<class_pattern> <method_pattern>
:指定要观测的类名+方法名。-t
:记录下方法每次执行的情况,如tt -t demo.MathGame primeFactors
。-i <index>
:查看某条执行记录的执行详情,如tt -i 1000
。-d <index>
:删除某条执行记录,配合-i
使用,tt- d -i 1000
。-n
:设置执行次数,如tt -t -n 10 demo.MathGame primeFactors
。-l
:显示目前已存在的所有执行记录。-p
:重新执行某条执行记录,配合-i
使用,如tt -i 1001 -p
。-s
:通过OGNL
表达式进行查找。-M
:指定接收结果的字节上限,默认为1KB
。---replay-times
:配合-p
使用,指定重新执行N
次。--replay-interval
:执行多次时,每次执行时的间隔时间。3
次某记录,每次间隔500ms
:tt -i 1001 -p --replay-times 3 --replay-interval 500
。monitor
:对指定的方法执行进行监控,使用参考,请参见 官方文档
monitor 对匹配 class-pattern
/method-pattern
/condition-express
的类、方法的调用进行监控。
monitor
命令是一个非实时返回命令.
实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C
为止。
服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何 Arthas 命令不会引起原有业务逻辑的改变。
可选项如下:
-c
:指定监控的周期,默认为60s
。-n
:指定监控的周期次数。monitor -c 10 -n 3 demo.MathGame primeFactors
stack
:输出当前方法被调用的调用路径。
很多时候我们都知道一个方法被执行,但这个方法被执行的路径非常多,或者你根本就不知道这个方法是从那里被执行了,此时你需要的是 stack 命令。
使用参考,请参见 官方文档
trace
命令能主动搜索 class-pattern
/method-pattern
对应的方法调用路径,渲染和统计整个调用链路上的所有性能开销和追踪调用链路。
使用参考,请参见 官方文档
trace
:方法内部调用路径,并输出方法路径上的每个节点上耗时,可选项如下:
-i
:跳过JVM的本地方法。-n
:和之前的-n
同义。rthas中的很多进阶操作都需要依赖于OGNL
表达式进行编写,在线上排查时,OGNL表达式往往会结合tt、watch、monitor、stack、trace
等多个命令共同使用。
arthas执行ognl表达式,获取对应的jvm对象数据。
ognl express -c {hashCode} --classLoaderClass {当前的全路径 ClassLoader 信息} -x {number}
参数名称 | 参数说明 |
---|---|
express | 执行的表达式 |
[c:] | 执行表达式的 ClassLoader 的 hashcode,默认值是 SystemClassLoader |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[x] | 结果对象的展开层次,默认值 1 |
ognl ‘@类的全限定名@静态属性名’
示例:
[arthas@80573]$ ognl '@demo.MathGame@random'
ognl ‘@类的全限定名@静态方法名(“参数”)’
示例1:调用入参为基本数据类型和集合的方法:
[arthas@80573]$ ognl '@demo.MathGame@print(100,{1,2,3,4})' -x 1
null
ognl ‘new 类的全限定名()’
示例1:调用无参创建对象
[arthas@80573]$ ognl 'new java.lang.Object()'
示例2:调用有参创建对象
[arthas@80573]$ ognl 'new xxx.xx.xxx("xx",x,{1,2,3})'
示例1:读取引用对象类型的属性值
[arthas@80573]$ ognl '@类全限定名@方法名("参数").属性名称'
示例2:读取List
类型的指定元素
[arthas@80573]$ ognl '@类全限定名@方法名("参数")[下标]'
详细的OGNL
语法可参考:官方指南
Arthas中集成了大部分JDK工具的功能实现,
在线上情况时,主要使用场景:
404
thread -n 10
命令查看CPU占用资源最高的10条线程。thread
命令查看线程的执行信息,定位到具体的方法。monitor
命令对目标方法进行监控,查看方法的调用次数与耗时。monitor
命令查询出的结果,定位问题根源,确定是由于调用过于频繁导致的,还是内部代码逻辑问题。jad
命令反编译class
文件,根据前面分析的原因排查代码并改善。thread
筛选所有阻塞状态的线程。stack
命令查看方法堆栈信息。jad
工具反编译源码,分析业务逻辑代码并改善。Arthas
来检测死锁特别简单,只需要执行一行命令thread -b
即可。trace
命令排查方法执行速度,trace xx类 xx方法 '#cost>50ms'
,观测执行时间大于50ms
的该方法的调用信息。trace -E ClassA|ClassB method1|method2|method3
。上线之后,发现代码有一处小地方存在逻辑错误需要更改,可以直接线上修改,而不用重启。
jad
将要修改的类反编译为.java
文件,输出到指定目录。.java
文件后,通过mc
命令重新编译.java
文件。retransform
命令将刚编译的.class
文件再次加载到JVM中。总之,Arthas通过与目标应用程序交互的命令行界面,用户可以方便地执行各种诊断和分析任务,以解决性能问题、内存泄漏等。
这种实现原理使Arthas成为一个强大的Java诊断工具。
Arthas是一个用于诊断和分析Java应用程序的开源工具,它的实现原理主要包括以下几个关键方面:
Java Agent
Arthas作为一个Java诊断工具,通过Java Agent技术来实现对目标Java应用程序的监控和诊断。Java Agent允许Arthas以字节码级别修改和增强目标应用程序的类,从而使其具备监控和诊断的能力。
Instrumentation API
Arthas使用Java的Instrumentation API来实现Java Agent。Instrumentation API允许Arthas在目标应用程序加载类的过程中插入字节码增强代码,以收集信息、执行命令或修改应用程序的行为。
字节码增强
Arthas通过字节码增强技术来修改目标应用程序的类。它可以在类加载期间动态地修改类的字节码,以添加监控、日志记录或诊断代码。这使得Arthas能够捕获应用程序的运行时信息,并执行一系列诊断和分析任务。
命令行工具
提供了一个交互式的命令行界面,用户可以在命令行中输入各种命令来与目标应用程序进行交互。这些命令可以查询应用程序的状态、分析性能问题、诊断内存泄漏等。
类加载和类转换
Arthas在应用程序启动时作为Java Agent被加载,然后通过Instrumentation API监视目标应用程序的类加载过程。一旦目标应用程序加载了新的类,Arthas可以拦截类加载事件并对类进行必要的字节码增强。
类加载器隔离
Arthas采用了一种类加载器隔离的机制,以确保Arthas的Agent类加载器与目标应用程序的类加载器相互隔离,防止冲突和干扰。
总之,Arthas的实现原理基于Java Agent、Instrumentation API和字节码增强技术,使其能够动态地监控、诊断和分析运行中的Java应用程序。
基于Java Agent、Instrumentation的产品,除了arthas,常用的还有pinpoint、skywalking这些非常有名气 的产品。
关于Java Agent/Instrumentation 的知识,请参见尼恩的博客:
关于skywalking 的架构和源码,请参见尼恩的视频:
《第24章视频:资深架构必备,彻底穿透Skywalking链路跟踪源码、JavaAgent探针技术》
Java Agent、Instrumentation、arthas 相关的面试题,是非常常见的面试题。
以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。
最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。很多小伙伴刷完后, 吊打面试官, 大厂横着走。
在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。
尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。
狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” !
……完整版尼恩技术圣经PDF集群,请找尼恩领取
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓