了解Arthas-7788
文章目录
- 1.简介
- 2.下载
- 3.启动
- 4.常见的命令
- 4.1 stack
- 4.2 jad
- 4.3 sc
- 4.4 sm
- 4.5 watch
- 4.6 trace
- 4.7 dashboard
- 4.8 thread
- 4.9 redefine
- 4.10 retransform
- 4.11 mc
- 4.12 火焰图
- 5.使用场景
- 5.1 慢接口定位
- 5.2 线程阻塞
- 5.3 内存泄漏精准捕获
- 5.4 热修复代码拯救崩溃
- 5.5 未打印日志,不知道入参
1.简介
Arthas 是Alibaba开源的Java诊断工具,动态跟踪Java代码;实时监控JVM状态,可以在不中断程序执行的情况下轻松完成JVM相关问题排查工作 。支持JDK 6+,支持Linux/Mac/Windows。
2.下载
https://arthas.aliyun.com/doc/quick-start.html
3.启动
java -jar arthas-boot.jar
4.常见的命令
4.1 stack
作用: 输出当前方法被调用的调用路径
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 执行次数限制 |
[m <arg>] | 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch <arg>] 。 |
举例:
# 输出MathGame类下的primeFactors方法的调用路径5次
stack com.linging.MathGame primeFactors -n 5
# 输出如下:
ts=2025-08-16 09:07:55.256;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@33909752@com.linging.MathGame.primeFactors()at com.linging.MathGame.run(MathGame.java:24)at com.linging.MathGame.main(null:16)
4.2 jad
作用: 反编译指定已加载类的源码
有时候,版本发布后,代码竟然没有执行,代码是最新的吗,这时可以使用jad反编译相应的class。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
[c:] | 类所属 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
举例:
# 查看反编译某个类的源代码
jad com.linging.MathGame
# 查看反编译某个方法的源代码
jad com.linging.MathGame primeFactors
# 查看反编译某个方法的源代码
ClassLoader:
+-jdk.internal.loader.ClassLoaders$AppClassLoader@33909752+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@74469453Location:
/C:/Users/Linging/Desktop/arthastest/springboot-arthas-1.0-SNAPSHOT.jarpublic List<Integer> primeFactors(int number) {
/*44*/ if (number < 2) {
/*45*/ ++this.illegalArgumentCount;throw new IllegalArgumentException("number is: " + number + ", need >= 2");}ArrayList<Integer> result = new ArrayList<Integer>();
/*50*/ int i = 2;
/*51*/ while (i <= number) {
/*52*/ if (number % i == 0) {
/*53*/ result.add(i);
/*54*/ number /= i;
/*55*/ i = 2;continue;}
/*57*/ ++i;}
/*61*/ return result;}
4.3 sc
作用: 查看JVM已加载的类信息
“Search-Class” 的简写,有的时候,你只记得类的部分关键词,你可以用sc获取完整名称 当你碰到这个错的时候“ClassNotFoundException”或者“ClassDefNotFoundException”,你可以用这个命令验证下
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 输出当前类的详细信息,包括这个类所加载的原始文件来源、类的声明、加载的 ClassLoader 等详细信息。 如果一个类被多个 ClassLoader 所加载,则会出现多次 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[f] | 输出当前类的成员变量信息(需要配合参数-d 一起使用) |
[x:] | 指定输出静态变量时属性的遍历深度,默认为 0,即直接使用 toString 输出 |
[c:] | 指定 class 的 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[n:] | 具有详细信息的匹配类的最大数量(默认为 100) |
[cs <arg>] | 指定 class 的 ClassLoader#toString() 返回值。长格式[classLoaderStr <arg>] |
举例:
# 模糊查找有关MathGame这个关键字的类,并输出当前类的详细信息和成员变量信息
sc *MathGame* -d -f
class-info com.linging.MathGamecode-source /C:/Users/Linging/Desktop/arthastest/springboot-arthas-1.0-SNAPSHOT.jarname com.linging.MathGameisInterface falseisAnnotation falseisEnum falseisAnonymousClass falseisArray falseisLocalClass falseisMemberClass falseisPrimitive falseisSynthetic falsesimple-name MathGamemodifier publicannotationinterfacessuper-class +-java.lang.Objectclass-loader +-jdk.internal.loader.ClassLoaders$AppClassLoader@33909752+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@74469453classLoaderHash 33909752fields name randomtype java.util.Randommodifier private,staticvalue java.util.Random@71ae9438name illegalArgumentCounttype intmodifier privateAffect(row-cnt:1) cost in 11 ms.
4.4 sm
作用: 查看已加载类的方法信息
“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。
sm
命令只能看到由当前类所声明 (declaring) 的方法,父类则无法看到。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
[d] | 展示每个方法的详细信息 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[c:] | 指定 class 的 ClassLoader 的 hashcode |
[classLoaderClass:] | 指定执行表达式的 ClassLoader 的 class name |
[n:] | 具有详细信息的匹配类的最大数量(默认为 100) |
举例:
# 模糊查找有MathGame关键字的类下的以pri前缀的方法,并展示每个方法的详细信息
sm *MathGame* pri* -d
declaring-class com.linging.MathGamemethod-name printmodifier public,staticannotationparameters intjava.util.Listreturn voidexceptionsclassLoaderHash 33909752declaring-class com.linging.MathGamemethod-name primeFactorsmodifier publicannotationparameters intreturn java.util.ListexceptionsclassLoaderHash 33909752Affect(row-cnt:2) cost in 19 ms.
4.5 watch
作用: 函数执行数据观测
可以监测一个方法的入参和返回值和抛出的异常,有些问题线上会出现,本地重现不了,这时这个命令就有用了
参数说明: watch 的参数比较多,主要是因为它能在 4 个不同的场景观察对象
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 函数名表达式匹配 |
express | 观察表达式,默认值:{params, target, returnObj} |
condition-express | 条件表达式 |
[b] | 在函数调用之前观察 |
[e] | 在函数异常之后观察 |
[s] | 在函数返回之后观察 |
[f] | 在函数结束之后(正常返回和异常返回)观察 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[x:] | 指定输出结果的属性遍历深度,默认为 1,最大值是 4 |
[m <arg>] | 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch <arg>] 。 |
举例:
# 观测 primeFactors 方法的入参、返回值、抛出的异常,执行1次,遍历深度2
watch com.linging.MathGame primeFactors '{params,returnObj,throwExp}' -n 1 -x 2
# 抛出异常的情况
Affect(class count: 1 , method count: 1) cost in 27 ms, listenerId: 18
method=com.linging.MathGame.primeFactors location=AtExceptionExit
ts=2025-08-16 09:54:14.922; [cost=0.1756ms] result=@ArrayList[@Object[][@Integer[-14820],],null,java.lang.IllegalArgumentException: number is: -14820, need >= 2at com.linging.MathGame.primeFactors(MathGame.java:46)at com.linging.MathGame.run(MathGame.java:24)at com.linging.MathGame.main(Unknown Source)
,
]# 为抛出异常的情况
Affect(class count: 1 , method count: 1) cost in 38 ms, listenerId: 19
method=com.linging.MathGame.primeFactors location=AtExit
ts=2025-08-16 09:54:29.041; [cost=2.1028ms] result=@ArrayList[@Object[][@Integer[1],],@ArrayList[@Integer[2],@Integer[63743],],null,
]
4.6 trace
作用: 输出方法内部调用路径,并输出方法路径上的每个节点上耗时
可以通过这个命令,查看哪些方法耗性能,从而找出导致性能缺陷的代码,这个耗时还包含了arthas执行的时间哦。
参数说明:
参数名称 | 参数说明 |
---|---|
class-pattern | 类名表达式匹配 |
method-pattern | 方法名表达式匹配 |
condition-express | 条件表达式 |
[E] | 开启正则表达式匹配,默认为通配符匹配 |
[n:] | 命令执行次数,默认值为 100。 |
#cost | 方法执行耗时 |
[m <arg>] | 指定 Class 最大匹配数量,默认值为 50。长格式为[maxMatch <arg>] 。 |
举例:
# 输出primeFactors方法的内部调用路径和耗时,输出5次,并不跳过jdk的方法
trace com.linging.MathGame primeFactors -n 5 --skipJDKMethod false
Affect(class count: 1 , method count: 1) cost in 64 ms, listenerId: 20
`---ts=2025-08-16 10:00:27.001;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@33909752`---[1.213ms] com.linging.MathGame:primeFactors() [throws Exception]+---[3.43% 0.0416ms ] java.lang.StringBuilder:<init>() #46+---[4.64% min=0.011ms,max=0.0239ms,total=0.0563ms,count=3] java.lang.StringBuilder:append() #46+---[1.73% 0.021ms ] java.lang.StringBuilder:toString() #46+---[2.93% 0.0355ms ] java.lang.IllegalArgumentException:<init>() #46`---throw:java.lang.IllegalArgumentException #46 [number is: -86450, need >= 2]`---ts=2025-08-16 10:00:28.018;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@33909752`---[0.3529ms] com.linging.MathGame:primeFactors() [throws Exception]+---[5.13% 0.0181ms ] java.lang.StringBuilder:<init>() #46+---[12.78% min=0.0133ms,max=0.0162ms,total=0.0451ms,count=3] java.lang.StringBuilder:append() #46+---[3.91% 0.0138ms ] java.lang.StringBuilder:toString() #46+---[11.62% 0.041ms ] java.lang.IllegalArgumentException:<init>() #46`---throw:java.lang.IllegalArgumentException #46 [number is: -96535, need >= 2]`---ts=2025-08-16 10:00:29.022;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@33909752`---[0.4732ms] com.linging.MathGame:primeFactors() [throws Exception]+---[3.85% 0.0182ms ] java.lang.StringBuilder:<init>() #46+---[9.64% min=0.0131ms,max=0.0168ms,total=0.0456ms,count=3] java.lang.StringBuilder:append() #46+---[2.81% 0.0133ms ] java.lang.StringBuilder:toString() #46+---[10.00% 0.0473ms ] java.lang.IllegalArgumentException:<init>() #46`---throw:java.lang.IllegalArgumentException #46 [number is: -122314, need >= 2]`---ts=2025-08-16 10:00:30.027;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@33909752`---[0.5817ms] com.linging.MathGame:primeFactors() [throws Exception]+---[2.20% 0.0128ms ] java.lang.StringBuilder:<init>() #46+---[47.58% min=0.0096ms,max=0.2533ms,total=0.2768ms,count=3] java.lang.StringBuilder:append() #46+---[1.70% 0.0099ms ] java.lang.StringBuilder:toString() #46+---[6.12% 0.0356ms ] java.lang.IllegalArgumentException:<init>() #46`---throw:java.lang.IllegalArgumentException #46 [number is: -196611, need >= 2]`---ts=2025-08-16 10:00:31.038;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=jdk.internal.loader.ClassLoaders$AppClassLoader@33909752`---[0.4237ms] com.linging.MathGame:primeFactors()+---[9.11% 0.0386ms ] java.util.ArrayList:<init>() #49`---[19.24% min=0.0121ms,max=0.0408ms,total=0.0815ms,count=4] java.util.List:add() #53
4.7 dashboard
作用: 查看当前系统的实时数据面板,按 ctrl+c 退出。
参数说明:
参数名称 | 参数说明 |
---|---|
[i:] | 刷新实时数据的时间间隔 (ms),默认 5000ms |
[n:] | 刷新实时数据的次数 |
- ID: Java 级别的线程 ID,注意这个 ID 不能跟 jstack 中的 nativeID 一一对应。
- NAME: 线程名
- GROUP: 线程组名
- PRIORITY: 线程优先级, 1~10 之间的数字,越大表示优先级越高
- STATE: 线程的状态
- CPU%: 线程的 cpu 使用率。比如采样间隔 1000ms,某个线程的增量 cpu 时间为 100ms,则 cpu 使用率=100/1000=10%
- DELTA_TIME: 上次采样之后线程运行增量 CPU 时间,数据格式为
秒
- TIME: 线程运行总 CPU 时间,数据格式为
分:秒
- INTERRUPTED: 线程当前的中断位状态
- DAEMON: 是否是 daemon 线程
举例:
# ==================================================线程信息================================================
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIM TIME INTERRUPT DAEMON
1 main main 5 TIMED_WA 0.0 0.000 0:2.031 false false
34 arthas-command-execute system 5 TIMED_WA 0.0 0.000 0:1.578 false true
33 arthas-NettyHttpTelnetBootstr system 5 RUNNABLE 0.0 0.000 0:1.265 false true
5 Attach Listener system 5 RUNNABLE 0.0 0.000 0:0.046 false true
27 arthas-NettyHttpTelnetBootstr system 5 RUNNABLE 0.0 0.000 0:0.015 false true
31 arthas-session-manager system 5 TIMED_WA 0.0 0.000 0:0.015 false true
2 Reference Handler system 10 RUNNABLE 0.0 0.000 0:0.000 false true
3 Finalizer system 8 WAITING 0.0 0.000 0:0.000 false true
4 Signal Dispatcher system 9 RUNNABLE 0.0 0.000 0:0.000 false true
21 Notification Thread system 9 RUNNABLE 0.0 0.000 0:0.000 false true
24 arthas-timer system 5 WAITING 0.0 0.000 0:0.000 false true
# ==================================================内存和GC信息================================================
Memory used total max usage GC
heap 30M 48M 4024M 0.76% gc.g1_young_generation.count 13
g1_eden_space 10M 14M -1 71.43% gc.g1_young_generation.time(m 65
g1_old_gen 20M 32M 4024M 0.50% s)
g1_survivor_space 345K 2048K -1 16.85% gc.g1_old_generation.count 0
nonheap 51M 53M -1 96.07% gc.g1_old_generation.time(ms) 0
codeheap_'non-nmethods' 1M 3M 7M 17.59%
metaspace 31M 32M -1 99.29%
# ==================================================运行环境信息================================================
Runtime
os.name Windows 11
os.version 10.0
java.version 17.0.15
java.home E:\java\java17\jdk17
systemload.average -1.00
processors 16
timestamp/uptime Sat Aug 16 10:17:14 CST 2025/4319s
Process ends after 1 time(s).
4.8 thread
作用: 查看当前线程信息,查看线程的堆栈
参数说明:
参数名称 | 参数说明 |
---|---|
id | 线程 id |
[n:] | 指定最忙的前 N 个线程并打印堆栈 |
[b] | 找出当前阻塞其他线程的线程 |
[i <value> ] | 指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
[–all] | 显示所有匹配的线程 |
举例:
# 查看线程id=1的堆栈信息
thread 1
"main" Id=1 TIMED_WAITINGat java.base@17.0.15/java.lang.Thread.sleep(Native Method)at java.base@17.0.15/java.lang.Thread.sleep(Thread.java:346)at java.base@17.0.15/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)at app//com.linging.MathGame.main(Unknown Source)
# 查看所有线程,dashboard只能固定展示前10个cpu使用率降序的线程,通过这个可以查看全部线程
thread -all
Threads Total: 15, NEW: 0, RUNNABLE: 9, BLOCKED: 0, WAITING: 2, TIMED_WAITING: 4, TERMINATED: 0
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIM TIME INTERRUPT DAEMON
2 Reference Handler system 10 RUNNABLE 0.0 0.000 0:0.000 false true
3 Finalizer system 8 WAITING 0.0 0.000 0:0.000 false true
4 Signal Dispatcher system 9 RUNNABLE 0.0 0.000 0:0.000 false true
5 Attach Listener system 5 RUNNABLE 0.0 0.000 0:0.046 false true
21 Notification Thread system 9 RUNNABLE 0.0 0.000 0:0.000 false true
24 arthas-timer system 5 WAITING 0.0 0.000 0:0.000 false true
27 arthas-NettyHttpTelnetBootstr system 5 RUNNABLE 0.0 0.000 0:0.015 false true
28 arthas-NettyWebsocketTtyBoots system 5 RUNNABLE 0.0 0.000 0:0.000 false true
29 arthas-NettyWebsocketTtyBoots system 5 RUNNABLE 0.0 0.000 0:0.000 false true
30 arthas-shell-server system 5 TIMED_WA 0.0 0.000 0:0.000 false true
31 arthas-session-manager system 5 TIMED_WA 0.0 0.000 0:0.015 false true
33 arthas-NettyHttpTelnetBootstr system 5 RUNNABLE 0.0 0.000 0:1.484 false true
34 arthas-command-execute system 5 RUNNABLE 0.0 0.000 0:1.671 false true
1 main main 5 TIMED_WA 0.0 0.000 0:2.281 false false
22 Common-Cleaner InnocuousThrea 8 TIMED_WA 0.0 0.000 0:0.000 false true
thread -b # 显示阻塞线程# thread -b(block 的缩写)会只列出当前处于 BLOCKED 状态、正在等待 monitor 锁的线程,并把**“谁占着锁”**也一起打出来。一次典型的输出包含以下字段:
"Thread-1" Id=25 cpuUsage=12% BLOCKED on java.lang.Object@5f4da5c3 owned by "Thread-2" Id=26at com.xxx.ServiceA.update(ServiceA.java:42)- blocked on <0x5f4da5c3> (a java.lang.Object)- locked <0x1a2b3c4d> (a java.util.HashMap)"Thread-2" Id=26 cpuUsage=87% RUNNABLEat com.xxx.ServiceB.batch(ServiceB.java:99)- locked <0x5f4da5c3> (a java.lang.Object)
- 线程名(Thread-1)
- 线程 ID(Id=25)
- CPU 使用率(cpuUsage=12%)
- 线程状态(BLOCKED)
- 阻塞目标(BLOCKED on java.lang.Object@5f4da5c3)
- 锁持有者(owned by “Thread-2” Id=26)
- 当前栈顶(ServiceA.java:42)
- 锁详情(blocked on / locked <地址>)
如果当前 没有任何线程处于 BLOCKED,thread -b 会输出:
No most blocking thread found!
总结
thread -b
就是一把“锁冲突探照灯”:
- 谁在等锁、
- 谁把锁占了、
- 各自跑到哪行代码,一目了然。
4.9 redefine
作用: redefine jvm已加载的类 ,可以在不重启项目的情况下,热更新类。
官方明确推荐 retransform,redefine 已过时。
维度 | redefine | retransform |
---|---|---|
原理 | 直接整体替换类的字节码 | 基于上一次版本做增量修改 |
是否保留历史 | ❌ 不保留;再次 retransform 会回到 redefine 前的版本 | ✅ 保留;可多次叠加或回退 |
与 watch/trace 的兼容性 | ❌ 冲突;执行 watch/trace 后会被重置 | ✅ 无冲突 |
能否撤销 | ❌ 不能 | ✅ retransform --deleteAll 一键撤销 |
官方态度 | 已不推荐使用 | ✅ 官方文档、社区一致推荐 |
典型场景 | 紧急一次性修复 | 日常热更新、调试、AOP 叠加 |
4.10 retransform
作用: 加载外部的.class
文件,retransform jvm 已加载的类。
使用步骤:
# 使用jad将当前类写出到当前目录下
jad --source-only com.linging.MathGame > MathGame.java# 修改类的内容# 使用mc重新编译,并把编译后的class文件写道tmp下
mc MathGame.java -d /tmp# 热更新代码
retransform /tmp/com/linging/MathGame.class
每次更新都会记录一个retransform entry,如果调试完成后,需要将retransform entry删除,并重新出发retransform。
如果不清除掉所有的 retransform entry,并重新触发 retransform ,则 arthas stop 时,retransform 过的类仍然生效。
# 查看所有 retransform entry
retransform -l
Id ClassName TransformCount LoaderHash LoaderClassName
1 com.linging.MathGame 2 null null
2 com.linging.MathGame 4 null null
# 删除指定 retransform entry
retransform -d 1# 删除所有 retransform entry
retransform --deleteAll# 显式触发 retransform
retransform --classPattern com.linging.MathGame
retransform 的限制:
- 不允许新增加 field/method
- 正在跑的函数,没有退出不能生效,比如下面新增加的
System.out.println
,只有run()
函数里的会生效
4.11 mc
作用: Memory Compiler/内存编译器,编译.java
文件生成.class
。
使用:
# 编译
mc /tmp/Test.java# -c 指定类加载器编译
mc -c 327a647b /tmp/Test.java# 通过classLoaderClass指定类加载器的类
mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp# -d 指定输出目录:/tmp/ClassA.java、/tmp/ClassB.java ==> /tmp/output
mc -d /tmp/output /tmp/ClassA.java /tmp/ClassB.java
4.12 火焰图
火焰图把 CPU 采样数据画成一张“火焰”:横条越宽 ⇒ 该函数占用 CPU 越多;层数越高 ⇒ 调用栈越深。
一眼就能找到“平顶”热点,进而判断是单次执行慢还是调用次数多,从而精准优化。
-
坐标含义
• X 轴:所有样本的汇总,宽度 = 该函数在采样里出现的比例(不是时间轴)。
• Y 轴:调用栈深度,最顶是当前正在执行的函数,往下是它的父函数。 -
一眼能看出的关键信息
场景 火焰图表现 结论与行动 某个方法横条特别宽 占 CPU 最多 优先优化:算法、缓存、并发 同一层出现多个宽条 各分支都耗时 存在多个热点,可分别治理 栈特别深且每层都窄 调用链过长 减少中间调用或引入批处理 绿色(Java)少,黄色/橙色多 大量 JNI / 系统调用 检查 IO、锁、系统交互 -
典型颜色含义(async-profiler 默认配色)
• 绿色:Java 字节码
• 黄色:JVM C++ 代码
• 橙色:内核态 C
• 红色:用户态 C/C++ -
快速落地步骤
profiler start # 开始 CPU 采样
# ……让业务跑一段……
profiler stop --format html # 结束并生成 html 火焰图
浏览器打开生成的 arthas-output/xxx.html
即可交互查看,点击任意框还能下钻到源码级。
- 实战案例速览
• Jackson 序列化占 25% 宽度 → 去掉无用字段 & 使用 Afterburner,CPU 下降 20%
• Netty 事件循环占宽顶 → 发现频繁 System.gc(),调大堆 + 换 G1,尖刺消失
总结:
火焰图把“谁吃了 CPU”可视化:宽度即罪证。看到平顶就重点盯,配合 Arthas 的 trace
、jad
、retransform
可快速完成“定位 → 优化 → 验证”闭环。
5.使用场景
5.1 慢接口定位
# 打印接口内部的调用耗时情况
trace com.example.OrderService getOrderById '#cost>1000' -n 5
5.2 线程阻塞
# 显示阻塞线程
thread -b
5.3 内存泄漏精准捕获
# 找出内存中实例对象个数top50的类
图形化工具visualVM可以查看# 查询指定类在内存中存活的对象个数(嫌疑类)
vmtool --action getInstances --className java.lang.StringBuffer --express 'instances.length'# 观察实例个数是否一直增长
5.4 热修复代码拯救崩溃
# 反编译、修改java文件、内存编译成class、热更新
jad、mc、retransform
5.5 未打印日志,不知道入参
# 观测、打印调用路径
watch、stack