Java开发工具——Arthas线上查询工具
本文详细介绍了 Java 开发工具 Arthas 的安装、启动及使用方法。内容涵盖本地安装与启动、容器安装与启动、远程登录与连接,以及基础和常用命令的实战应用。通过 Arthas,开发者可以高效地对 Java 应用进行线上排错和监控,提升开发效率。
摘要
本文主要介绍了 Java 开发工具 Arthas 的安装、启动及使用。包括本地安装与启动、容器安装与启动、基础命令与使用以及常用命令与使用等内容。通过 Arthas,开发者可以方便地对 Java 应用进行线上排错和监控,提高开发效率。

1. Arthas 本地安装与启动
1.1. Arthas的安装
# 下载Arthas工具包
curl -O https://arthas.aliyun.com/arthas-boot.jar
# 启动Arthas服务
java -jar arthas-boot.jar

1.2. Arthas 插件下载


2. Arthas 容器安装与启动
很多时候,应用在 docker 里出现 arthas 无法工作的问题,是因为应用没有安装 JDK ,而是安装了 JRE 。如果只安装了 JRE,则会缺少很多 JAVA 的命令行工具和类库,Arthas 也没办法正常工作。下面介绍两种常见的在 Docker 里使用 JDK 的方式。
2.1. 通过包管理软件来安装
Arthas安装到基础镜像
FROM openjdk:8-jdk-alpine
# copy arthas
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
# Install OpenJDK-8
RUN apt-get update && \
apt-get install -y openjdk-8-jdk && \
apt-get install -y ant && \
apt-get clean;
# Fix certificate issues
RUN apt-get update && \
apt-get install ca-certificates-java && \
apt-get clean && \
update-ca-certificates -f;
# Setup JAVA_HOME -- useful for docker commandline
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/
RUN export JAVA_HOME
或者使用
RUN yum install -y \
java-1.8.0-openjdk \
java-1.8.0-openjdk-devel
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk/
RUN export JAVA_HOME
2.2. 诊断Docker里的Java进程(服务器联网状态)
# 使用Arthas诊断docker 容器服务
docker exec -it ${containerId} /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"
2.3. Docker对应服务器无法联网联网
2.3.1. 本地下载 arthas-boot.jar
wget https://arthas.aliyun.com/arthas-boot.jar
2.3.2. 复制文件到容器
docker cp arthas-boot.jar <container_id>:/tmp/arthas-boot.jar
2.3.3. 登录容器执行 Arthas
docker exec -it <container_id> /bin/bash
java -jar /tmp/arthas-boot.jar
接下来根据提示选择 Java 进程即可 attach。
2.4. 诊断k8s里容器里的Java进程(服务器联网状态)
kubectl exec -it ${pod} --container ${containerId} -- /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"
2.5. k8s 对应服务器无法联网联网
2.5.1. 下载 arthas-boot.jar 到本地
wget https://arthas.aliyun.com/arthas-boot.jar
2.5.2. 拷贝到 Pod 中(支持压缩传输)
kubectl cp arthas-boot.jar <namespace>/<pod>:/tmp/arthas-boot.jar -c <container_name>
2.5.3. 登录 Pod 并执行 Arthas
kubectl exec -it <pod> -c <container_name> -- /bin/sh
java -jar /tmp/arthas-boot.jar
3. Arthas 远程登录与连接
3.1. Arthas Web Console登录
http://IP:8563/

3.2. Arthas Tunnel Server集群管理
通过 Arthas Tunnel Server/Client 来远程管理/连接多个 Agent。比如,在流式计算里,Java 进程可以是在不同的机器启动的,想要使用 Arthas 去诊断会比较麻烦,因为用户通常没有机器的权限,即使登陆机器也分不清是哪个 Java 进程。在这种情况下,可以使用 Arthas Tunnel Server/Client。
3.2.1. 下载Arthas Tunnel Server (https://github.com/alibaba/arthas/releases)

3.2.2. 启动 arthas 时连接到 tunnel server
在启动 arthas,可以传递--tunnel-server参数,attach 成功之后,会打印出 agentId,比如:
as.sh --tunnel-server 'ws://127.0.0.1:7777/ws'
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki https://arthas.aliyun.com/doc
tutorials https://arthas.aliyun.com/doc/arthas-tutorials.html
version 3.1.2
pid 86183
time 2019-08-30 15:40:53
id URJZ5L48RPBR2ALI5K4V
如果是启动时没有连接到 tunnel server,也可以在后续自动重连成功之后,通过 session 命令来获取 agentId:
[arthas@86183]$ session
Name Value
-----------------------------------------------------
JAVA_PID 86183
SESSION_ID f7273eb5-e7b0-4a00-bc5b-3fe55d741882
AGENT_ID URJZ5L48RPBR2ALI5K4V
TUNNEL_SERVER ws://47.75.156.201:80/ws

4. Arthas 基础命令与使用
4.1. dashboard(查询进程所有信息)
实时展示 Java 应用当前线程、内存、GC、运行环境等关键指标,是 Arthas 最常用的系统监控入口命令。

$ 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
|
ID |
NAME |
GROUP |
PRIORI |
STATE |
%CPU |
TIME |
INTERRUPT |
DAEMON |
|
线程 ID,系统唯一标识 |
线程名称(如 nioEventLoop、main 等) |
所属线程组(如 system、main) |
线程优先级(默认 5,一般很少调整) |
当前线程状态,如 WAITING、RUNNABLE、TIMED_WAITING 等 |
当前线程的 CPU 占用百分比(最近时间片) |
线程运行的总时间(从 JVM 启动开始计算) |
线程是否被中断(true/false) |
是否是守护线程(true 表示后台线程,不会阻止 JVM 退出) |
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)
|
heap |
ps_eden_space |
ps_survivor_space |
ps_old_gen |
nonheap |
code_cache |
|
Java 堆使用情况(主要存储对象) |
年轻代 Eden 区 |
年轻代 Survivor 区 |
老年代,长期存活对象 |
非堆区(类元数据、方法区、常量池等) |
JIT 编译生成的机器代码缓存 |
注意:ps_* 是 Parallel Scavenge 垃圾回收器的标志(说明当前 JVM 使用的是 PS GC),
Java堆 = Eden区 + Survivor区(S0 + S1)+ 老年代
|
gc.ps_scavenge.count |
gc.ps_scavenge.time |
gc.ps_marksweep.count |
gc.ps_marksweep.time |
|
PS 新生代垃圾收集次数 |
PS 新生代 GC 总耗时(毫秒) |
老年代 GC 次数 |
老年代 GC 总耗时 |
说明:
- 新生代 GC 发生了 4 次,总共耗时 166 毫秒
- 老年代未发生 GC,说明内存压力不大
4.2. thread (查询进程信息)
|
参数名称 |
参数说明 |
|
id |
线程 id |
|
[n:] |
指定最忙的前 N 个线程并打印堆栈 |
|
[b] |
找出当前阻塞其他线程的线程 |
|
[i ] |
指定 cpu 使用率统计的采样间隔,单位为毫秒,默认值为 200 |
|
[--all] |
显示所有匹配的线程 |
查看当前线程信息,查看线程的堆栈。这里的 cpu 使用率与 linux 命令top -H -p <pid> 的线程%CPU类似,一段采样间隔时间内,当前 JVM 里各个线程的增量 cpu 时间与采样间隔时间的比例。
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)
4.2.1. thread id, 显示指定线程的运行堆栈
[arthas@22888]$ thread 24
"xxl-job, admin JobScheduleHelper#scheduleThread" Id=24 TIMED_WAITING
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:342)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.xxl.job.admin.core.thread.JobScheduleHelper$1.run(JobScheduleHelper.java:202)
at java.lang.Thread.run(Thread.java:750)
|
行号 |
内容 |
含义 |
|
|
|
这是线程的名字,表明该线程是 |
|
|
|
线程 ID 是 24,当前状态是 |
|
|
|
线程正在执行 |
|
|
逐级调用栈信息:显示线程是怎么运行到当前这一步的 |
从 |
|
|
|
这是你关心的业务代码部分,调度线程当前在 |
|
|
|
线程入口是 |
这说明:
- 这个线程是 XXL-JOB 的调度线程,名字为:
JobScheduleHelper#scheduleThread - 当前处于
TIMED_WAITING状态,即线程执行了Thread.sleep(),正在等待下一次调度周期到来 - 它没有卡死、也没有异常,只是在正常等待
- 属于预期行为(定时任务调度器大多使用 sleep/wait 控制节奏)
4.2.2. thread -b, 找出当前阻塞其他线程的线程:
thread -b (查询阻塞线程)
$ thread -b
"http-bio-8080-exec-4" Id=27 TIMED_WAITING
at java.lang.Thread.sleep(Native Method)
at test.arthas.TestThreadBlocking.doGet(TestThreadBlocking.java:22)
- locked java.lang.Object@725be470 <---- but blocks 4 other threads!
at javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at test.filter.TestDurexFilter.doFilter(TestDurexFilter.java:46)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at com.taobao.tomcat.valves.ContextLoadFilterValve$FilterChainAdapter.doFilter(ContextLoadFilterValve.java:191)
at com.taobao.eagleeye.EagleEyeFilter.doFilter(EagleEyeFilter.java:81)
at com.taobao.tomcat.valves.ContextLoadFilterValve.invoke(ContextLoadFilterValve.java:150)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:429)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
- locked org.apache.tomcat.util.net.SocketWrapper@7127ee12
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Number of locked synchronizers = 1
- java.util.concurrent.ThreadPoolExecutor$Worker@31a6493e
4.2.3. 支持一键展示当前最忙的前 N 个线程并打印堆栈:
[arthas@22888]$ thread -n 2
"C1 CompilerThread2" [Internal] cpuUsage=0.36% deltaTime=0ms time=14085ms
"arthas-command-execute" Id=19733 cpuUsage=0.17% deltaTime=0ms time=16ms RUNNABLE
at sun.management.ThreadImpl.dumpThreads0(Native Method)
at sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:448)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:206)
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.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
4.3. jad (反编译代码)
jad 包名 类
示例:
jad --source-only com.zhuangxiaoyan.hyxftest.bridge.UrgentMessageStrategy send
$ 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);
}
catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
}
}
public static void print(int number, List<Integer> primeFactors) {
StringBuffer sb = new StringBuffer("" + number + "=");
Iterator<Integer> iterator = primeFactors.iterator();
while (iterator.hasNext()) {
int factor = iterator.next();
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
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.
4.4. watch(查询函数调用输入输出)
同时看入参和返回值,也可以这样:
watch 包名 函数名 '{params,returnObj,throwExp}' -n 5 -x 3
示例:
watch com.zhuangxiaoyan.hyxftest.bridge.UrgentMessageStrategy send '{params,returnObj,throwExp}' -n 5 -x 3
通过watch命令来查看demo.MathGame#primeFactors函数的返回值:
$ watch demo.MathGame primeFactors "{params,target,returnObj}" -x 2 -b -s -n 2
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 46 ms.
ts=2018-12-03 19:29:54; [cost=0.01696ms] result=@ArrayList[
@Object[][
@Integer[1],
],
@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13038],
],
null,
]
ts=2018-12-03 19:29:54; [cost=4.277392ms] result=@ArrayList[
@Object[][
@Integer[1],
],
@MathGame[
random=@Random[java.util.Random@522b408a],
illegalArgumentCount=@Integer[13038],
],
@ArrayList[
@Integer[2],
@Integer[2],
@Integer[2],
@Integer[5],
@Integer[5],
@Integer[73],
@Integer[241],
@Integer[439],
],
]
- watch 命令定义了 4 个观察事件点,即
-b函数调用前,-e函数异常后,-s函数返回后,-f函数结束后 - 4 个观察事件点
-b、-e、-s默认关闭,-f默认打开,当指定观察点被打开后,在相应事件点会对观察表达式进行求值并输出 - 这里要注意
函数入参和函数出参的区别,有可能在中间被修改导致前后不一致,除了-b事件点params代表函数入参外,其余事件都代表函数出参 - 当使用
-b时,由于观察事件点是在函数调用前,此时返回值或异常均不存在 - 在 watch 命令的结果里,会打印出
location信息。location有三种可能值:AtEnter,AtExit,AtExceptionExit。对应函数入口,函数正常 return,函数抛出异常。
4.5. arthas (服务退出与关闭)
// 服务关闭
quit
exit
如果只是退出当前的连接,可以用quit或者exit命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。
// 服务退出
stop
如果想完全退出 arthas,可以执行stop命令。
5. Arthas 常用命令与使用
5.1. getstatic 查询类的静态属性
# 使用方法为getstatic class_name field_name
$ getstatic demo.MathGame random
field: random
@Random[
serialVersionUID=@Long[3905348978240129619],
seed=@AtomicLong[120955813885284],
multiplier=@Long[25214903917],
addend=@Long[11],
mask=@Long[281474976710655],
DOUBLE_UNIT=@Double[1.1102230246251565E-16],
BadBound=@String[bound must be positive],
BadRange=@String[bound must be greater than origin],
BadSize=@String[size must be non-negative],
seedUniquifier=@AtomicLong[-3282039941672302964],
nextNextGaussian=@Double[0.0],
haveNextNextGaussian=@Boolean[false],
serialPersistentFields=@ObjectStreamField[][isEmpty=false;size=3],
unsafe=@Unsafe[sun.misc.Unsafe@2eaa1027],
seedOffset=@Long[24],
]
5.2. monitor(方法执行监控)
monitor 包名 函数名 -n 10 --cycle 10
monitor com.zhuangxiaoyan.hyxftest.bridge.UrgentMessageStrategy send -n 10 --cycle 10
对匹配 class-pattern/method-pattern/condition-express的类、方法的调用进行监控。monitor 命令是一个非实时返回命令. 实时返回命令是输入之后立即返回,而非实时返回的命令,则是不断的等待目标 Java 进程返回信息,直到用户输入 Ctrl+C 为止。
服务端是以任务的形式在后台跑任务,植入的代码随着任务的中止而不会被执行,所以任务关闭后,不会对原有性能产生太大影响,而且原则上,任何 Arthas 命令不会引起原有业务逻辑的改变。
|
监控项 |
说明 |
|
timestamp |
时间戳 |
|
class |
Java 类 |
|
method |
方法(构造方法、普通方法) |
|
total |
调用次数 |
|
success |
成功次数 |
|
fail |
失败次数 |
|
rt |
平均 RT |
|
fail-rate |
失败率 |
|
参数名称 |
参数说明 |
|
class-pattern |
类名表达式匹配 |
|
method-pattern |
方法名表达式匹配 |
|
condition-express |
条件表达式 |
|
[E] |
开启正则表达式匹配,默认为通配符匹配 |
|
|
统计周期,默认值为 120 秒 |
|
[b] |
在方法调用之前计算 condition-express |
|
|
指定 Class 最大匹配数量,默认值为 50。长格式为 |
$ monitor -c 5 demo.MathGame primeFactors
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 94 ms.
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:38 demo.MathGame primeFactors 5 1 4 1.15 80.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:43 demo.MathGame primeFactors 5 3 2 42.29 40.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:48 demo.MathGame primeFactors 5 3 2 67.92 40.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:53 demo.MathGame primeFactors 5 2 3 0.25 60.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:06:58 demo.MathGame primeFactors 1 1 0 0.45 0.00%
timestamp class method total success fail avg-rt(ms) fail-rate
-----------------------------------------------------------------------------------------------
2018-12-03 19:07:03 demo.MathGame primeFactors 2 2 0 3182.72 0.00%
5.3. retransform(动态热加载新的文件)
|
类,类加载相关的命令 |
说明 |
|
sc |
Search Class 查看运行中的类信息 |
|
sm |
Search Method 查看类中方法的信息 |
|
jad |
反编译字节码为源代码 |
|
mc |
Memory Compile 将源代码编译成字节码 |
|
redefine |
将编译好的字节码文件加载到jvm中运行 |
5.3.1.1. 下载jar 包
下载demo-arthas-spring-boot.jar(https://github.com/hengyunabc/spring-boot-inside/blob/master/demo-arthas-spring-boot/demo-arthas-spring-boot.jar),再用java -jar命令启动:
# 访问接口
curl http://localhost:80/user/0
# 返回结果
{"timestamp":1550223186170,"status":500,"error":"Internal Server Error","exception":"java.lang.IllegalArgumentException","message":"id < 1","path":"/user/0"}
5.3.1.2. Arthas使用jad反编译UserController
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
# 修改UserController文件
vim /tmp/UserController.java
# 文件如下
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("retransform test id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "retransform name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}
5.3.1.3. Arthas中使用sc查找加载UserController的ClassLoader
# sc 命令
sc -d *UserController | grep classLoaderHash
# sc 命令结果
$ sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3
可以发现是 spring boot LaunchedURLClassLoader@1be6f5c3 加载的。
注意hashcode是变化的,需要先查看当前的ClassLoader信息,提取对应ClassLoader的hashcode。
如果你使用-c,你需要手动输入hashcode:-c <hashcode>
对于只有唯一实例的ClassLoader可以通过--classLoaderClass指定class name,使用起来更加方便.
--classLoaderClass 的值是ClassLoader的类名,只有匹配到唯一的ClassLoader实例时才能工作,目的是方便输入通用命令,而-c <hashcode>是动态变化的。
5.3.1.4. Athas中使用mc 编译修改后的文件
保存好/tmp/UserController.java之后,使用mc(Memory Compiler)命令来编译,并且通过--classLoaderClass参数指定ClassLoader:
# mc 编译修改的新的文件
mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
# 命令结果如下:
$ mc --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms
5.3.1.5. retransform加载新class 文件
# retransform 命令
retransform /tmp/com/example/demo/arthas/user/UserController.class
# retransform 命令结果如下:
$ retransform /tmp/com/example/demo/arthas/user/UserController.class
retransform success, size: 1
5.3.1.6. 重新访问:

5.4. reset (重置增强类)
重置增强类,将被 Arthas 增强过的类全部还原,Arthas 服务端stop时会重置所有增强过的类。
$ trace Test test
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 57 ms.
`---ts=2017-10-26 17:10:33;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@14dad5dc
`---[0.590102ms] Test:test()
`---ts=2017-10-26 17:10:34;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@14dad5dc
`---[0.068692ms] Test:test()
$ reset Test
Affect(class-cnt:1 , method-cnt:0) cost in 11 ms.
$ trace Test test
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 15 ms.
`---ts=2017-10-26 17:12:06;thread_name=main;id=1;is_daemon=false;priority=5;TCCL=sun.misc.Launcher$AppClassLoader@14dad5dc
`---[0.128518ms] Test:test()
# 还原所有类
$ reset
Affect(class-cnt:1 , method-cnt:0) cost in 9 ms.
博文参考
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)