core文件怎么分析_Arthas源码分析

本文详细介绍了Arthas如何实现对Java程序的分析和诊断,包括Arthas的核心功能、Instrumentation和ASM字节码增强技术的使用。通过分析启动剖析、加载Agent的过程,以及Command的执行机制,特别是WatchCommand的字节码增强,揭示了Arthas的强大诊断能力。
摘要由CSDN通过智能技术生成

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进行启动

5836482972e422cb4c78697b84e1240c.png

* parsearguments 解析用户输入参数,并获取用户输入的pid * attachjvm对选中的java程序进行挂载 * active_console 启动arthas的客户端,显示命令行界面
其中attach_jvm是其中最核心的功能点,它的主要作用是启动arthas-core.jar

e94ff1367c221c59eab00a0e3a4e50fb.png

在arthas-core中主要的功能其实就是加载agent.jar到对应的java程序中

71127278fd6c18fbb9022abea6dad16e.png

上图可以看出,程序先用获取所有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的启动类。

e8c3a7da59ee15f1ec396c89429ce976.png

上面图片中显示了premain和agentmain两个方法。这两个方法有些不同,premain方法用于运行前的agent加载,agentmain用于运行时的加载。在arthas中使用的是agentmain方法。 那么jar是怎么封装成agent进行使用的呢?可以打开pom.xml文件,使用assembly进行打包的时候制定了manifest文件的一些信息

5a75e3b4101eba191013cd4f3b3145e6.png


回到AgentBootstrap类,agentmain方法里面调用了main方法。

9672410a108b1404d819a5d415ef4292.png

从上图可知,main方法实现以下几点功能

  • 获取arthas-spy.jar,用bootstrapClassLoader进行加载
  • 创建自定义的类加载器
  • 初始化探针,加载com.taobao.arthas.core.advisor.AdviceWeaver中的methodOnBegin、methodOnReturnEnd、methodOnThrowingEnd等等方法(下面章节将具体介绍为何要加载探针和AdviceWeaver中的方法)
  • 加载com.taobao.arthas.core.server.ArthasBootstrap,调用bind方法,启动server服务

启动server

4b5623108d96f221543cedd937b14be1.png

上图图片中根据telnetPort判断是否启动telnet服务,根据httpPort判断是否启动http服务。
下面以TelnetTermServer服务为例进行分析

5f47ecf8b23627510e48222d729c6b79.png

当有命令过来时,通过termHandler.handle进行处理。 这里的termHandler来自于上一步设置的TermServerTermHandler

675d77ab3ff3adf46a32206beba26cd1.png

handle方法中使用shellServer进行处理,而这里的shellServer实际上是ShellServerImpl这个类

675d77ab3ff3adf46a32206beba26cd1.png

ShellServerImpl中handleTerm用于处理客户端的连接

6ca2b30c04e0177e7e16482ef8117127.png

这里的session就是客户端的连接,readline会等待客户端的输入。
命令执行
深入readline方法,我们会发现底层代码其实就是调用了ShellLineHandler.handle对命令进行处理。这里面的封装比较复杂,ShellLineHandler.handle->RequestHandler.accept->Interaction.end,最终封装成Interaction进行处理,这里就不一一罗列。

c0935e916f9782f26a0a3c8478fc7e52.png

仔细研读ShellLineHandler.handle中的代码,我们会发现每个用户请求,arthas都会封装成job。

7d31870ac3364bf012e5e7a4511101b2.png

b562cea8807e3d908de7a42df9b9bf82.png

0f1936b98507a20b43345477063ff84c.png

job根据命令名称从commandManager获取预设的command进行执行,而BuiltinCommandPack里面加载了所有的命令

1be9213d9acee33321d347041d8f45d8.png

最后进行命令的执行

bf4733831cc8903e756f7462745c7cab.png


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方法,对指定的类进行字节码编辑

4a319c87dcb8fac55ad5cacde25e7c46.png

Enhancer

83e81bd7eb1f48cd8e7daa8cf75978bd.png

AdviceWeaver类实现了ClassVisitor接口。ClassVisitor接口的核心是visit和visitMethod,其中visitMethod是对类中每个方法的访问。 visitMethod
在visitMethod方法中,重写了一个AdviceAdapter类,继承了MethodVisitor类,实现对method的访问

97e7c2e9f5cd3d23a652c32e5879d1c2.png

onMethodEnter
在访问方法前执行onMethodEnter中的内容。

3a2d360c929122bf236ccc8b20e26b5f.png

调用loadAdviceMethod方法,根据里面的代码显示,实质是获取Spy这个类中的变量ONBEFOREMETHOD,通过ASM的方法进行调用。其中loadArrayForBefore方法是加载ONBEFOREMETHOD方法对应的参数,而ONBEFOREMETHOD这个方法对应的是AdviceWeaver类中的methodOnBegin方法。 这一步在哪里设置的呢
回到最开始的AgentBootstrap.initSpy方法,我们就可以发现。实际上spy这个类使用bootstrapClassLoader加载的,以确保之后能够在各个类加载器中能够被正确获取

4bb50baa9403e3ba0fd258620f946dc7.png

在这里不得不佩服开发人员的厉害之处,在一开始的初始化Spy的时候将Spy这类设置在根加载器中,确保自定义加载器都能够获取到这个类。通过spy这个类作为探针,用ASM获取到这个静态变量

6d94666af736e1ca2ea3f1c879fddf67.png

1467a0e1e2e6a2a7880249d35c469b7d.png

db369c6820bf414b2dd8f4cc17c4a7b3.png

用loadArrayForBefore加载所需要的参数(需要对字节码指令、局部变量表、操作数栈有一定了解),最后使用invokeVirtual指令进行调用。

4349fa61ee22c19fc1a9375eebc5b64b.png

ON_BEFORE_METHOD对应着methodOnBegin方法,其他暂不展示,以methodOnBegin为例。

894da761d1f49919ad5017d48a26c21c.png
  • 将主要信息放在栈中,并存入线程变量,在methodOnReturnEnd等方法中可以使用
  • 进行方法前置通知,listener在EnhancerCommand.enhance方法中注册。WatchComand对应的监听器是WatchAdviceListener。

WatchAdviceListener

9bc85ded48e22591d487b618ea1680f7.png

eeb99ec8bb7b0a2bd8ca4f107c19bf51.png

判断是否满足ognl表达式,如果满足,向客户端打印信息。这里仅仅是用methodBegin做分析,如果是methodExit即方法结束,也会进行后置通知,执行WatchAdviceListener.afterReturning方法。
总结
希望通过以上的分析,大家能够大致了解arthas运行的原理。通过arthas源码的研读,我们会找到一些有趣的东西,比如Instrumentation、ASM的用法。这些在以后工作中遇到问题时给我们带来启发。

dcef68bc2949c20ea9f70fa4cc1e7db7.png


需要获取更多的视频资料,详情源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料【关注+私信回复【学习】】获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值