一、写在前面
前几天阅读了一下arthas相关源码,准备写一下心得,发现有人已经写了,而且写得很好,这里就不在重复,下面转一下相关文章,用于自己学习记录
二、Arthas
Arthas是一个java在线诊断工具,能够分析、诊断、定位java应用问题。之前余梦同学对Arthas的使用写过一篇文章在线分析诊断工具Arthas简介及使用,所以具体使用方法我就不重复叙述了。接下来我将详细地分析下arthas是如何实现对java程序的分析和诊断的。
前言
Arthas是一个功能非常强大的诊断工具,功能点很多,例如:jvm信息、线程信息、搜索类中的方法、跟踪代码执行、观测方法的入参和返回参数等等。这些会驱使你去了解它是如何做到的。在这之前你可能还需要了解一些额外的知识,例如ava SE 5中增加的特性Instrumentation、ASM字节码增强技术。
Instrumentation把 Java的instrument 功能从本地代码中解放出来,使之可以用Java代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。Instrumentation是Java SE 5中的新特性。在SE 5中只能在运行前进行加载,在SE 6中实现能够在运行时加载。例如java -javaagent: agent.jar -jar agent-demo.jar
ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。asm字节码增强技术主要是用来反射的时候提升性能的,如果单纯用jdk的反射调用,性能是非常低下的,而使用字节码增强技术后反射调用的时间已经基本可以与直接调用相当。 ASM相对于其他类似工具如BCEL、SERP、Javassist、CGLIB,它的最大的优势就在于其性能更高,其jar包仅30K。Hibernate和Spring都使用了cglib代理,而cglib本身就是使用的ASM,可见ASM在各种开源框架都有广泛的应用。 ASM框架中的核心类有以下几个: ① ClassReader:该类用来解析编译过的class字节码文件。 ② ClassWriter:该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字节码文件。 ③ ClassAdapter:该类也实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。
启动剖析
1.通过as.sh进行启动
- parsearguments 解析用户输入参数,并获取用户输入的pid attachjvm对选中的java程序进行挂载 active_console 启动arthas的客户端,显示命令行界面
其中attach_jvm是其中最核心的功能点,它的主要作用是启动arthas-core.jar
在arthas-core中主要的功能其实就是加载agent.jar到对应的java程序中
上图可以看出,程序先用获取所有java的VirtualMachine,根据已知的pid找到对应的VirtualMachine,然后进行VirtualMachine.loadAgent加载arthas.jar。这里加载agent在主程序启动之后获取pid进行运行后加载的,这个特性是Java SE 6的新特性。可以在运行后对加载的class进行重加载。
加载Agent
想要理解虚拟机如何加载agent,那么就需要找到agent的启动方法,并理解agent如何对运行时的class进行修改。 找到com.taobao.arthas.agent.AgentBootstrap这个类,它是agent的启动类。
上面图片中显示了premain和agentmain两个方法。这两个方法有些不同,premain方法用于运行前的agent加载,agentmain用于运行时的加载。在arthas中使用的是agentmain方法。 那么jar是怎么封装成agent进行使用的呢?可以打开pom.xml文件,使用assembly进行打包的时候制定了manifest文件的一些信息
回到AgentBootstrap类,agentmain方法里面调用了main方法。
从上图可知,main方法实现以下几点功能
获取arthas-spy.jar,用bootstrapClassLoader进行加载
创建自定义的类加载器
初始化探针,加载com.taobao.arthas.core.advisor.AdviceWeaver中的methodOnBegin、methodOnReturnEnd、methodOnThrowingEnd等等方法(下面章节将具体介绍为何要加载探针和AdviceWeaver中的方法)
加载com.taobao.arthas.core.server.ArthasBootstrap,调用bind方法,启动server服务
启动server
上图图片中根据telnetPort判断是否启动telnet服务,根据httpPort判断是否启动http服务。
下面以TelnetTermServer服务为例进行分析
当有命令过来时,通过termHandler.handle进行处理。 这里的termHandler来自于上一步设置的TermServerTermHandler
handle方法中使用shellServer进行处理,而这里的shellServer实际上是ShellServerImpl这个类
ShellServerImpl中handleTerm用于处理客户端的连接
这里的session就是客户端的连接,readline会等待客户端的输入。
命令执行
深入readline方法,我们会发现底层代码其实就是调用了ShellLineHandler.handle对命令进行处理。这里面的封装比较复杂,ShellLineHandler.handle->RequestHandler.accept->Interaction.end,最终封装成Interaction进行处理,这里就不一一罗列。
仔细研读ShellLineHandler.handle中的代码,我们会发现每个用户请求,arthas都会封装成job。
job根据命令名称从commandManager获取预设的command进行执行,而BuiltinCommandPack里面加载了所有的命令
最后进行命令的执行
Command分析
arthas里面预设了很多的命令,接下来我们将根据WatchCommand进行详细分析。除了需要字节码增强的命令外其他的命令比较简单,主要是使用java.lang.management包的类来获取虚拟机信息。
WatchCommand
WatchCommand主要用于观测方法的入参和返回参数信息,以及方法的耗时统计。使用ASM字节码技术对特定class进行字节码增强,并重新加载class使之生效。因此在研读这个命令之前请先了解ASM和class文件规范。public class WatchCommand extends EnhancerCommand,WatchCommand继承于EnhancerCommand。
详细查看EnhancerCommand.enhance方法中的Enhancer.enhance方法。1.筛选出需要增强的类,根据className进行条件过滤。2.构建增强器。3.Instrumentation.retransformClasses重新加载类,重新加载时会触发ClassFileTransformer.transform方法,对指定的类进行字节码编辑
Enhancer
AdviceWeaver类实现了ClassVisitor接口。ClassVisitor接口的核心是visit和visitMethod,其中visitMethod是对类中每个方法的访问。
visitMethod
在visitMethod方法中,重写了一个AdviceAdapter类,继承了MethodVisitor类,实现对method的访问
onMethodEnter
在访问方法前执行onMethodEnter中的内容。
调用loadAdviceMethod方法,根据里面的代码显示,实质是获取Spy这个类中的变量ONBEFOREMETHOD,通过ASM的方法进行调用。其中loadArrayForBefore方法是加载ONBEFOREMETHOD方法对应的参数,而ONBEFOREMETHOD这个方法对应的是AdviceWeaver类中的methodOnBegin方法。
这一步在哪里设置的呢
回到最开始的AgentBootstrap.initSpy方法,我们就可以发现。实际上spy这个类使用bootstrapClassLoader加载的,以确保之后能够在各个类加载器中能够被正确获取
在这里不得不佩服开发人员的厉害之处,在一开始的初始化Spy的时候将Spy这类设置在根加载器中,确保自定义加载器都能够获取到这个类。通过spy这个类作为探针,用ASM获取到这个静态变量
用loadArrayForBefore加载所需要的参数(需要对字节码指令、局部变量表、操作数栈有一定了解),最后使用invokeVirtual指令进行调用。
ON_BEFORE_METHOD对应着methodOnBegin方法,其他暂不展示,以methodOnBegin为例。
将主要信息放在栈中,并存入线程变量,在methodOnReturnEnd等方法中可以使用
进行方法前置通知,listener在EnhancerCommand.enhance方法中注册。WatchComand对应的监听器是WatchAdviceListener。
WatchAdviceListener
判断是否满足ognl表达式,如果满足,向客户端打印信息。这里仅仅是用methodBegin做分析,如果是methodExit即方法结束,也会进行后置通知,执行WatchAdviceListener.afterReturning方法。
总结
希望通过以上的分析,大家能够大致了解arthas运行的原理。通过arthas源码的研读,我们会找到一些有趣的东西,比如Instrumentation、ASM的用法。这些在以后工作中遇到问题时给我们带来启发。(转载来源https://zhuanlan.zhihu.com/p/53984185)