6到飞起的Java诊断工具Arthas,你用过吗?

记得前段时间遇到了一个页面加载过长的问题,当时就想排查下在哪一步消耗的时间比较长,由于是线上问题,第一反应就是有没有什么办法可以无侵入式的查询调用链路耗时呢?

这时 Arthas 走进了我的眼帘,并成功帮我定位到了问题,就是这样引起了我对 Arthas 的兴趣,于是花了点时间对 Arthas 作了一个了解。

什么是 Arthas

摘录一段官方 Github 上的简介

《6到飞起的Java诊断工具Arthas,你用过吗?》

Arthas 基于哪些工具开发而来

greys-anatomy: Arthas代码基于Greys二次开发而来,非常感谢Greys之前所有的工作,以及Greys原作者对Arthas提出的意见和建议!

termd: Arthas的命令行实现基于termd开发,是一款优秀的命令行程序开发框架,感谢termd提供了优秀的框架。

crash: Arthas的文本渲染功能基于crash中的文本渲染功能开发,可以从这里看到源码,感谢crash在这方面所做的优秀工作。

cli: Arthas的命令行界面基于vert.x提供的cli库进行开发,感谢vert.x在这方面做的优秀工作。

compiler: Arthas里的内存编绎器代码来源

Apache Commons Net: Arthas里的Telnet Client代码来源

JavaAgent:运行在 main方法之前的拦截器,它内定的方法名叫 premain ,也就是说先执行 premain 方法然后再执行 main 方法

ASM:一个通用的Java字节码操作和分析框架。它可以用于修改现有的类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从它们构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但是主要关注性能。因为它被设计和实现得尽可能小和快,所以非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)

工程目录

《6到飞起的Java诊断工具Arthas,你用过吗?》

arthas-agent:基于JavaAgent技术的代理

bin:一些启动脚本

arthas-boot:Java版本的一键安装启动脚本

arthas-client:telnet client代码

arthas-common:一些共用的工具类和枚举类

arthas-core:核心库,各种arthas命令的交互和实现

arthas-demo:示例代码

arthas-memorycompiler:内存编绎器代码

arthas-packaging:maven打包相关的

arthas-site:arthas站点

arthas-spy:编织到目标类中的各个切面

static:静态资源

arthas-testcase:测试

整体流程

首先我们先放出一张整体宏观的模块调用图,下面我们会按照整个 Arthas 启动流程逐步分析,红色部分本篇文章将不涉及,会在后续文章中单独分析

《6到飞起的Java诊断工具Arthas,你用过吗?》 启动方式介绍

使用 arthas-boot 启动(推荐)

下载 arthas-boot.jar,然后用 java -jar 的方式启动:

《6到飞起的Java诊断工具Arthas,你用过吗?》

可以加 -h 参数,打印帮助信息:

《6到飞起的Java诊断工具Arthas,你用过吗?》

如果下载速度比较慢,可以使用aliyun的镜像:

                                   java -jar arthas-boot.jar –repo-mirror aliyun –use-http

使用 as.sh 脚本启动

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲回车执行即可:

《6到飞起的Java诊断工具Arthas,你用过吗?》

上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在shell下面执行./as.sh,就会进入交互界面。

也可以执行./as.sh -h来获取更多参数信息。

Arthas 是如何启动的

既然官方推荐用 arthas-boot 启动,那下面我们就一起来看下 arthas-boot 是如何启动的。

首先我们在 arthas-boot 的 pom 文件中找到启动类:

《6到飞起的Java诊断工具Arthas,你用过吗?》

从pom文件中,我们可以发现arthas-boot的启动类为com.taobao.arthas.boot.Bootstrap,下面我们就去看看 Bootstrap 是如何启动 arthas 的,有兴趣的同学也可以自行看下另外一种启动方式as.sh。

归然将整个启动的过程全部通过注释在代码中体现出来了,所以:

《6到飞起的Java诊断工具Arthas,你用过吗?》

《6到飞起的Java诊断工具Arthas,你用过吗?》

《6到飞起的Java诊断工具Arthas,你用过吗?》

《6到飞起的Java诊断工具Arthas,你用过吗?》

《6到飞起的Java诊断工具Arthas,你用过吗?》
《6到飞起的Java诊断工具Arthas,你用过吗?》

《6到飞起的Java诊断工具Arthas,你用过吗?》

到此,Arthas 的启动流程就结束了,在这其中,我们发现了两个关键的 jar 包,arthas-core 和 arthas-agent,那么这两个jar又做了什么事情呢,咱们继续往下走,想要了解这两个jar包的作用,首先我们要先普及一个知识点——Java探针。

Java探针

Java探针主要涉及两个知识点:

JavaAgent

JavaAgent 是一种能够在不影响正常编译的情况下,修改字节码的技术。java作为一种强类型的语言,不通过编译就不能能够进行jar包的生成。

有了 JavaAgent 技术,就可以在字节码这个层面对类和方法进行修改。也可以把 JavaAgent 理解成一种代码注入的方式,但是这种注入比起 Spring的 AOP 更加的优美。

从JDK6开始,有两种代理方式:

        ·     通过命令行(-javaagent)的形式在应用程序启动前处理(premain方式)

        ·     在应用程序启动后的某个时机处理(agentmain方式)

ASM字节码

ASM 是一个通用的 Java 字节码操作和分析框架,它可以用于修改现有类或直接以二进制形式动态生成类。

ASM 提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。

ASM 提供与其他Java字节码框架类似的功能,但专注于性能。因为它的设计和实现尽可能小而且快,所以它非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。

ASM 用于许多项目,包括:

        ·     OpenJDK,生成lambda调用站点,以及Nashorn编译器

        ·     Groovy 编译器和 Kotlin 编译器

        ·     Cobertura 和 Jacoco,为了衡量代码覆盖率,仪器类

        ·     CGLIB,用于动态生成代理类(用于其他项目,如Mockito和EasyMock)

        ·     Gradle,在运行时生成一些类

明白了这两个知识点后,我们一起来看下 Arthas 中的 JavaAgent——arthas-agent

Arthas-Agent

首先我们从Pom文件看起,找到premain和agentmain

《6到飞起的Java诊断工具Arthas,你用过吗?》

从这里我们很清楚地看到了 premain 和 agentmain 的方法被放在了com.taobao.arthas.agent.AgentBootstrap中。

那么接下来我们就走进AgentBootstrap类中,了解下它的实现。

在AgentBootstrap类中,我们很快发现了这两个方法

《6到飞起的Java诊断工具Arthas,你用过吗?》

这两个方法都同时指向当前类中的main方法,并传递了两个参数,下面我们先对着两个参数做个解读

        ·     String args

这个参数是我们在 arthas-boot.jar 中启动 arthas-core.jar 时传入的参数

《6到飞起的Java诊断工具Arthas,你用过吗?》

        ·     Instrumentation inst

java.lang.instrument.Instrumentation 实例,由 JVM 自动传入,集中了几乎所有功能方法,如:类操作、classpath 操作等

了解了这两个参数以后,我们走进main方法看下实现

《6到飞起的Java诊断工具Arthas,你用过吗?》

这里主要重点讲下上面圈出来的两点,首先我们来看下第一段的代码。

《6到飞起的Java诊断工具Arthas,你用过吗?》

第一步先将我们的arthas-spy.jar添加到 BootstrapClassLoader 中,在 Java Instrumention 的实现中,这行代码应该是很常见的。为什么要这样做呢?

在Java中,Java类加载器分为 BootstrapClassLoader、ExtensionClassLoader和SystemClassLoader。

BootstrapClassLoader 主要加载的是JVM自身需要的类,由于双亲委派机制的存在,越基础的类由越上层的加载器进行加载,因此,如果需要在由 BootstrapClassLoader 加载的类的方法中调用由 SystemClassLoader 加载的arthas-spy.jar,这违反了双亲委派机制。

而arthas-spy.jar添加到 BootstrapClassLoader 的 classpath 中,由 BootstrapClassLoader加载,就解决了这个问题。

initSpy这个方法则使用ArthasClassloader加载com.taobao.arthas.core.advisor.AdviceWeaver类(在后续文章中会详细解读),并将其中的methodOnBegin、methodOnReturnEnd、methodOnThrowingEnd等方法取出,并赋给Spy类。后面在通过ASM做类增强的时候,Spy就是连接业务类和Arthas类的桥梁。

接着我们看下第二段蓝色框中的代码。这里面主要是做了一些服务端启动的事情。

《6到飞起的Java诊断工具Arthas,你用过吗?》

这段代码中,主要通过反射的手段,调用了ArthasBootstrap类中的bind方法来启动 Arthas 服务端,接下来我们就一起来看下 Arthas 服务端启动的源码。

Arthas服务端启动

废话不多说,先上代码。

《6到飞起的Java诊断工具Arthas,你用过吗?》

这段代码主要是围绕 ShellServer 做一些配置,并调用listen方法启动监听

《6到飞起的Java诊断工具Arthas,你用过吗?》

在listen方法中,主要是根据之前注册的TermServer来逐个启动,这里以TelnetTermServer为例讲解,接下来看下TelnetTermServer中的listen方法。

《6到飞起的Java诊断工具Arthas,你用过吗?》

我们跟踪下start代码,发现最后调用的是NettyTelnetBootstrap的start方法。

《6到飞起的Java诊断工具Arthas,你用过吗?》

主要是通过netty来启动网络服务。下面我们看下对输入的处理类TermServerTermHandler。

《6到飞起的Java诊断工具Arthas,你用过吗?》

主要是通过调用shellServer的handleTerm方法。

《6到飞起的Java诊断工具Arthas,你用过吗?》

这里的 session 就是客户端的连接,而readline方法就是用来处理用户的输入的。

至于每个命令是如何工作的,且听下回分解

总结

arthas中涉及到的知识点很多的了解

        ·     netty

        ·     termd

        ·     cli

        ·     asm

        ·     JavaAgent

大家如果感兴趣的话,可以花点时间研究下,相信这些框架会让大家受益匪浅。

欢迎工作一到五年的Java工程师朋友们加入我的个人粉丝群Java填坑之路:789337293

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用”没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

    原文作者:Java填坑之路
    原文地址: https://www.jianshu.com/p/c507405da119
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞