天天看点

jvm指令介绍及在线debug工具原理

Java Virtual Machine:

To understand the details of the bytecode, we need to discuss how a Java Virtual Machine (JVM) works regarding the execution of the bytecode. JVM is a platform-independent execution environment that converts Java bytecode into machine language and executes it. A JVM is a stack-based machine. Each thread has a JVM stack which stores frames. A frame is created each time a method is invoked, and consists of an operand stack, an array of local variables, and a reference to the runtime constant pool of the class of the current method.

jvm指令介绍及在线debug工具原理

Stack Based Virtual Machines:

We need to know a little about stack based VM to better understand Java Bytecode. A stack based virtual machine the memory structure where the operands are stored is a stack data structure. Operations are carried out by popping data from the stack, processing them and pushing in back the results in LIFO (Last in First Out) fashion. In a stack based virtual machine, the operation of adding two numbers would usually be carried out in the following manner (where 20, 7, and “result” are the operands):

jvm指令介绍及在线debug工具原理

idea->view->show bytecode(安装方式Preferences->Plugins->搜“Bytecode Viewer”)

jvm指令介绍及在线debug工具原理

ASMifier asm指令方式

ClassReader reader = new ClassReader(Hello.class.getName());

reader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(System.out)), ClassReader.SKIP_DEBUG);

静态方法与非静态方法的调用核心区别在于,本地变量数组的第一个元素是否是this,比如hello、hi两个空方法

初步了解操作栈、本地变量数组的“变化过程”

初始的local variables中的值分别为可选的this对象(静态方法没有)及方法参数,故可通过在方法开始时读取local variables获取方法的入参、修改入参等

在方法结束时返回值位于栈顶,可以读取“operand stack”获取方法的返回值

有人听过try catch会比较耗时吗?通过字节码分析一下原因

总结:通过TRYCATCHBLOCK、GOTO、LABEL方式可以方法增加try catch,也可以把异常吃掉等

问题:本例中local variables中index为2的没有被使用,why?

方法正常返回是operand stack栈顶为的返回值

方法的入参存储在local variables中,静态方法从0开始、非静态从1开始

try catch指令,提供了拦截异常的一种方式,甚至吃掉异常

cglib与javassist是高级的字节码工具,asm与bcel是低级的字节码工具(支持java字节码指令),低级的工具会更灵活,故选择asm来作为分析,asm文档参见

如何把增强的代码加入到当前的jvm中?spring已经给了我们答案,但是有两个疑问:

HelloService$EnhancerByCGLIB$41503a7e,为什么不是HelloService ?

答:HelloService已经被AppClassLoader加载(通常情况下),不能重复加载相同名称的class

bean的实例的getSuperClass()的值为什么是HelloService ?

答:HelloService helloService=(HelloService)getBean(“helloService”),如果动态生成的class不是被增强的class子类,强制转化报异常

AppClassLoader并未暴露defineClass方法,如何加载的增强类?

答:通过反射调用classloader.defineClass即可

故spring本质是对代理类做了一个子类,故除了对“代理类字节码”增强外,还需要修改“代理类字节码”的parent、及class name

DemoClassVisitor:对方法的增强,入参、返回值、异常、耗时

ChangeParentClassVisitor:修改类的parent

ChangeNameClassVisitor:修改类名称

注:asm代码主要使用了visitor、责任链模式,理解了这两个设计模式代码读起来比较容易

ChangeParentClassVisitor、ChangeNameClassVisitor不涉及jvm指令代码相对简单,故我们重点介绍DemoClassVisitor,它通过继承asm的工具类AdviceAdapter,分别实现onMethodEnter、onMethodExit,核心代码如下:

onMethodEnter中增加如下code:

onMethodEnter中最上面增加如下code:

onMethodExit中最下面增加如下code:

onMethodExit中增加如下code:

原始的HelloService代码

注意虽然调用的是HelloService,但实际运行是HelloServiceYqf

通过增强,我们获取导入入参、响应时间(该方法为void故无返回值)

注释掉onMethodExit中异常代码的dup及mv.visitInsn(Opcodes.ATHROW)代码即可,被拦截的方法必须没有返回值,否则需要在代码里生成默认值

控制台输出

注意main开头的打印代码未出现在控制台

Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能从本地代码中解放出来,使之可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。

在 Java SE 6 里面,instrumentation 包被赋予了更强大的功能:启动后的 instrument、本地代码(native code)instrument,以及动态改变 classpath 等等。这些改变,意味着 Java 具有了更强的动态控制、解释能力,它使得 Java 语言变得更加灵活多变。

在 Java SE6 里面,最大的改变使运行时的 Instrumentation 成为可能。在 Java SE 5 中,Instrument 要求在运行前利用命令行参数或者系统参数来设置代理类,在实际的运行之中,虚拟机在初始化之时(在绝大多数的 Java 类库被载入之前),instrumentation 的设置已经启动,并在虚拟机中设置了回调函数,检测特定类的加载情况,并完成实际工作。但是在实际的很多的情况下,我们没有办法在虚拟机启动之时就为其设定代理,这样实际上限制了 instrument 的应用。而 Java SE 6 的新特性改变了这种情况,通过 Java Tool API 中的 attach 方式,我们可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的。

偷懒把依赖的jar包也打进去,方便一些

代码只用到了DemoClassVisitor,并不需要替换名称、及父类,因为Java Tool API提供了“直接修改已加载的class字节码”的能力,和“类spring”方式不同

VirtualMachine及VirtualMachineDescriptor是非标准包,在windows下是没有的!

运行方式:先运行agentTarget启动待增强的jvm、再运行agentTest动态增强

注意是HelloService.sayHi,不是HelloServiceQyf.sayHi

spring的代码增强的原理,本质是通过classloader反射+asm字节码增强,去做一个目标类的子类加载到当前jvm

在线字节码工具原理,通过java agent方式动态的修改jvm被增强的class,实现获取入参、异常、返回值、耗时等