它主要解决的痛点:
- 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
- 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
- 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
- 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
- 是否有一个全局视角来查看系统的运行状况?
- 有什么办法可以监控到JVM的实时运行状态?
- 线上代码有错误,不想重新发布?那能不能改class文件替换一下?
看到它能解决这些问题,心里挺激动的,这不正是我们线上调试想要的吗^_^
# 支持环境
JDK 6+,Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能
# 安装
1、下载jar
默认推荐的安装方式,使用jar方式来启动
curl -O https://arthas.aliyun.com/arthas-boot.jarjava -jar arthas-boot.jar
其实,我写本文时,用的是Windows操作系统,我直接下载的
打印帮助信息
java -jar arthas-boot.jar -h
重点说明:--target-ip 一定要是arthas所在机器对外暴露的ip,默认是127.0.0.1,只能本地访问。
2、在线安装
此方式只适应于 Linux/Unix/Mac 等平台,命令如下:
curl -L https://arthas.aliyun.com/install.sh | sh
启动arthas
./as.sh PID #进程id 指定JAVA进程id ./as.sh -h #h来获取更多参数信
3、在docker中安装
#运行arthas-demodocker run --name arthas-demo -it hengyunabc/arthas:latest /bin/sh -c "java -jar /opt/arthas/arthas-demo.jar"#安装arthasdocker exec -it ${containerId} /bin/bash -c "wget https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar"
也可以很简单把Arthas安装到你的Docker镜像里,如下:
FROM openjdk:8-jdk-alpine# copy arthasCOPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
4、启动时,attach自身进程
1)Arthas Spring Boot Starter
目前只支持springboot 2,添加maven:
com.taobao.arthas arthas-spring-boot-starter ${arthas.version}
配置属性:
arthas.agent-id=hsehdfsfghhwertyfadarthas.tunnel-server=ws://47.75.156.201:7777/ws
通过访问http://localhost:8080/actuator/arthas,来查看agent-id,如下:
{ "arthasConfigMap": { "agent-id": "hsehdfsfghhwertyfad", "tunnel-server": "ws://47.75.156.201:7777/ws", }}
2)非spring boot应用
添加maven依赖:
com.taobao.arthas arthas-agent-attach ${arthas.version}com.taobao.arthas arthas-packaging ${arthas.version}
添加启动代码:
import com.taobao.arthas.agent.attach.ArthasAgent; public class ArthasAttachExample { public static void main(String[] args) { ArthasAgent.attach(); }}
# 远程连接
1、WebConsole
通过浏览器http访问8563端口,注意页面红色框内的IP也必须指定服务端的IP,不能用默认的127.0.0.1,如下:
2、telnet
说明一下,Windows默认telnet客服端没有开启,需要你手动开启一下。
3、tunnel server方式
下载相关jar包
启动:
java -jar arthas-tunnel-server.jar
默认情况下,arthas tunnel server的web端口是8080,arthas agent连接的端口是7777。
启动arthas,注册到tunnel server,并指定一个agent-id:mytest123(默认是随机产生):
java -jar arthas-boot.jar --tunnel-server 'ws://192.168.1.28:7777/ws' --agent-id mytest123
如下图所示,表面生成成功:
这时,便可以访问 http://localhost:8080/ ,再通过agentId连接到已注册的arthas agent上。
最后,放一张arthas tunnel server的架构示意图,有助于我们理解tunnel server和arthas agent的关系,见下图
# 常用命令
1、Dashboard 命令
查看当前系统的实时数据面板,例如:服务器thread信息、内存memory、GC回收等情况
2、Thread(线程监控)
$ thread -n 3"as-command-execute-daemon" Id=57 cpuUsage=72% RUNNABLEat 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:133)at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:79)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:370)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:748)Number of locked synchronizers = 1 - java.util.concurrent.ThreadPoolExecutor$Worker@a2f70c7
可以看到这个线程是被synchroned关键字锁导致的阻塞 ,目前只支持找出synchronized关键字阻塞住的线程, 如果是java.util.concurrent.Lock, 目前还不支持。
3、trace (当前方法内部调用路径,路径上每个节点的耗时)
$ trace #类名 #方法名
对于执行耗时相对较长的方法,调用链路耗时属性会高亮显示方便排查
4、JVM (jvm实时运行状态,内存使用情况等)
$ jvm RUNTIME -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- MACHINE-NAME 28679@iz2zehzeir87zi8q99krk1z JVM-START-TIME 2019-03-28 17:32:16MANAGEMENT-SPEC-VERSION 1.2 SPEC-NAME Java Virtual Machine Specification SPEC-VENDOR Oracle Corporation SPEC-VERSION 1.8 VM-NAME Java HotSpot(TM) 64-Bit Server VM VM-VENDOR Oracle Corporation VM-VERSION 25.191-b12 INPUT-ARGUMENTS [] CLASS-PATH demo-0.0.1-SNAPSHOT.jar BOOT-CLASS-PATH /usr/local/jdk/jre/lib/resources.jar:/usr/local/jdk/jre/lib/rt.jar:/usr/local/jdk/jre/lib/sunrsasign.jar:/usr/local/jdk/jre/lib/jsse.jar:/usr/local/jdk/jre/lib/jce.jar :/usr/local/jdk/jre/lib/charsets.jar:/usr/local/jdk/jre/lib/jfr.jar:/usr/local/jdk/jre/classes LIBRARY-PATH /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- MACHINE-NAME 28679@iz2zehzeir87zi8q99krk1z JVM-START-TIME 2019-03-28 17:32:16MANAGEMENT-SPEC-VERSION 1.2 SPEC-NAME Java Virtual Machine Specification SPEC-VENDOR Oracle Corporation SPEC-VERSION 1.8 VM-NAME Java HotSpot(TM) 64-Bit Server VM VM-VENDOR Oracle Corporation VM-VERSION 25.191-b12 INPUT-ARGUMENTS [] CLASS-PATH demo-0.0.1-SNAPSHOT.jar BOOT-CLASS-PATH /usr/local/jdk/jre/lib/resources.jar:/usr/local/jdk/jre/lib/rt.jar:/usr/local/jdk/jre/lib/sunrsasign.jar:/usr/local/jdk/jre/lib/jsse.jar:/usr/local/jdk/jre/lib/jce.jar :/usr/local/jdk/jre/lib/charsets.jar:/usr/local/jdk/jre/lib/jfr.jar:/usr/local/jdk/jre/classes LIBRARY-PATH /usr/java/packages/lib/amd64:/usr/lib64:/lib64:/lib:/usr/lib
5、watch
当前方法执行数据观测,能观察到的范围为:返回值、抛出异常、入参
$ trace #类名 #方法名 "{params,target,returnObj,throwExp }" OGNL 表达式 {params,target,returnObj,throwExp }throwExp:异常params :入参(数组),单个参数params【0】returnObj:返回值
$ watch com.example.demo.controller index2 "{params,target,returnObj}" -x 5Press Q or Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 81 ms.ts=2019-03-29 14:24:14; [cost=1000.746582ms] result=@ArrayList[ @Object[][ @String[辛志富], ], @controller[ ], @String[index2],]
6、stack
当前方法被调用的路径,显示当前方法被那些方法调用
$ monitor -c 4 com.example.demo.controller uuidTwoPress Q or Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 56 ms. timestamp class method total success fail avg-rt(ms) fail-rate -------------------------------------------------------------------------------------------------------- 2019-03-29 14:55:40 com.example.demo.controller uuidTwo 7 7 0 0.18 0.00%
7、monitor 命令
监控类、方法的调用进行监控,调用次数、成功次数、失败次数、平均响应时长、失败率等。
$ classloader #每种classloader加载类的个树 name numberOfInstances loadedCountTotal org.springframework.boot.loader.LaunchedURLClassLoader 1 4463 com.taobao.arthas.agent.ArthasClassloader 2 3631 BootstrapClassLoader 1 2961 java.net.FactoryURLClassLoader 1 835 sun.misc.Launcher$AppClassLoader 1 46 sun.reflect.DelegatingClassLoader 41 41 sun.misc.Launcher$ExtClassLoader 1 25 Affect(row-cnt:7) cost in 7 ms.$ classloader -t # 类加载器间的层级关系+-BootstrapClassLoader +-sun.misc.Launcher$ExtClassLoader@1959f618 +-com.taobao.arthas.agent.ArthasClassloader@5fc476c6 +-com.taobao.arthas.agent.ArthasClassloader@5017e14b +-sun.misc.Launcher$AppClassLoader@5c647e05 +-java.net.FactoryURLClassLoader@4ad317f0 +-org.springframework.boot.loader.LaunchedURLClassLoader@20ad9418 Affect(row-cnt:7) cost in 5 ms
8、classloader 命令
将JVM中所有的类加载器统计出来,树状展示
$ classloader #每种classloader加载类的个树 name numberOfInstances loadedCountTotal org.springframework.boot.loader.LaunchedURLClassLoader 1 4463 com.taobao.arthas.agent.ArthasClassloader 2 3631 BootstrapClassLoader 1 2961 java.net.FactoryURLClassLoader 1 835 sun.misc.Launcher$AppClassLoader 1 46 sun.reflect.DelegatingClassLoader 41 41 sun.misc.Launcher$ExtClassLoader 1 25 Affect(row-cnt:7) cost in 7 ms.$ classloader -t # 类加载器间的层级关系+-BootstrapClassLoader +-sun.misc.Launcher$ExtClassLoader@1959f618 +-com.taobao.arthas.agent.ArthasClassloader@5fc476c6 +-com.taobao.arthas.agent.ArthasClassloader@5017e14b +-sun.misc.Launcher$AppClassLoader@5c647e05 +-java.net.FactoryURLClassLoader@4ad317f0 +-org.springframework.boot.loader.LaunchedURLClassLoader@20ad9418 Affect(row-cnt:7) cost in 5 ms
# 实战案例
我在网上看到一个动态修改上线项目的案例,我觉得比较有意思,这里整理一下给大家分享一下。
这个案例主要做了一件事情,在不停机不重启不重新发包的情况下,手动在代码中去掉抛异常代码,具体要修改的代码如下:
启动服务也达到我们预期异常
具体替换代码的流程如下:
1、jad:将需要更改的文件先进行反编译,保存下来 ,编译器修改
$ jad --source--only com.example.demo.DemoApplication > /data/DemoApplication.java
2、SC:查找当前类是哪个classLoader加载的
$ sc -d *DemoApplication | grep classLoaderclassLoaderHash 20ad9418 #类加载器 编号
3、MC:用指定的classloader重新将类在内存中编译
$ mc -c 20ad9418 /data/DemoApplication.java -d /data Memory compiler output:/data/com/example/demo/DemoApplication.class
4、redefine:将编译后的类加载到JVM,上边编译后的.class文件地址
$ redefine /data/com/example/demo/DemoApplication.class redefine success, size: 1
文件替换后我们再次访问一下程序,发现异常没有了程序已经是我们修改正确后的,class文件替换成功
结合这个案例一看,阿里出品的这款神器简直要超神了,这么多命令,记不住怎么办呢?咱们下一篇继续告诉你该怎么来玩转这些命令~