Java Agent 核心技术JVM之类的热替换原理解读
2022-5-22 20:57:0 Author: mp.weixin.qq.com(查看原文) 阅读量:5 收藏

先讲讲怎么用吧

    一上来就说原理还是不怎么合适的,先给大家讲下这个技术怎么用吧。但是这篇文章重点不是讲怎么用,所以我只讲个大概流程。

第一步:写个Agent类,获取Instrumentation对象

public class MyAgent {  private static Instrumentation mInstrumentation;
public static void agentmain(String agentArgs, Instrumentation inst) { mInstrumentation = inst; }
// 拿到Instrumentation对象后就可以利用ClassModifierTransformer来进行类的热替换了 public static void modifyClass(Class clazz){ ClassFileTransformer transformer = new ClassModifierTransformer(); mInstrumentation.addTransformer(transformer, true); mInstrumentation.retransformClasses(new Class[]{clazz}); mInstrumentation.removeTransformer(transformer); }}

第二步:写个ClassFileTransformer,利用ASM/Javassist等工具进行字节码修改

public class ClassModifierTransformer implements ClassFileTransformer {
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // 在这里利用Javassist等工具修改类的字节码,返回修改后类的字节数组 return null; }}

    目前已经有很多文章讲具体使用方法了,大家可以搜索下,我这里先介绍两篇:

  • 基于Java Instrument的Agent实现

  • 谈谈Java Intrumentation和相关应用

热替换的核心就在于Instrumentation的两个方法:

 void addTransformer(ClassFileTransformer transformer, boolean canRetransform); void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

    addTransformer 用来注册类的修改器;retransformClasses 会让类重新加载,从而使得注册的类修改器能够重新修改类的字节码。

下面让我们重点讲讲这两个方法的实现:

1. addTransformer

      可见我们自己的实现的 ClassFileTransformer 被添加到了 TransformerManager中,让我们跟进去看看:

     ClassFileTransformer对象这次被放入了TransformerManager的一个数组中。    OK,注册完毕,很简单对不对?下面我们再来看下稍微复杂点的retransformClasses 吧。

2.retransformClasses

    这个方法的实现最终调用的是个Native方法。很多同学看到Native方法就头疼,不要急,Native方法也是人写的,不过是一段文本而已。我们来看下具体实现吧:

继续跟进 -->

retransformClasses 最后会调用到 jvmtiEnv.cpp中的 RetransformClasses


补充:Klass是一个抽象基类,它定义了一些接口(纯虚函数),由 InstanceKlass 继承并实现,两者结合可以描述一个java类的方法、字段、父类等信息。InstanceKlass 在jvm层面可以描述绝大部分java类。

上面这段代码主要干了两件事:

(1) 根据 java 层的Class对象,找到JVM层的类实例 InstanceKlass,并获取类的字节码,存放在class_definitions数组中。因为可以一次替换多个类,所以这里加了一个循环体,遍历每个要修改的类。

(2) 调用VMThread::execute(&op)

    在获取了类的字节码之后,创建了一个 VM_RedefineClasses 的 vmop,然后通知VMThread进行处理。

    在分析代码之前,先来看下比较重要的 VM_OperationVM_Operation 是虚拟机级别的操作,这些操作包含了所有JVM的内置操作,例如GC、获取线程栈等等。这个类是所有这些操作的基类,该类定义在hotspot/src/share/vm/runtime/vmOperations.hpp 。

    首先,该类定义了 Mode 和 VMOp_Type 两个枚举。第一个表示该操作的模式,第二个表示该操作的类型。事实上所有的类型都在文件开头的宏定义中写明了,这里我们只关心 RedefineClasses 这个类型。Mode包括这四种:

 再来看下 VM_RedefineClasses

    VM_RedefineClasses是VM_Operation的子类,实现了类转换的所有逻辑。该类定义和实现分别在hotspot/src/share/vm/prims/jvmtiRedefineClasses.hpp和hotspot/src/share/vm/prims/jvmtiRedefineClasses.cpp。

    对于一个VM_Operation的子类,首先需要关心 evaluation_mode 函数。VM_RedefineClasses 类中找不到该函数,因此它是一个需要在 safepoint 阻塞的操作。

    然后就是核心操作,即 doit_prologue、doit、doit_epilogue。代码比较复杂,本节先介绍doit_prologue的实现。我们先从注释上了解每个步骤做了什么

1.doit_prologue

    在 doit_prologue 阶段,整个操作都是在Java线程中进行的,因此不会阻塞VMThread,也不会被计入safepoint的耗时。注意整个源码中 the_class 表示待替换的类,scratch_class表示新的类。

    该阶段主要做的就是准备需要的字节码,包括解析字节码、类的链接、常量池合并、字节码校验等步骤。需要说明的是,如果业务代码中准备新的字节码时间比较长(前面提到的获取新字节码的回调也是在这里发生),这个阶段时间就会变长,但是不会阻塞JVM的核心线程。

    然后,我们看下这部分是如何实现的

   VMThread::execute(&op) 中会调用到 VM_RedefineClasses::doit_prologue,核心逻辑在 VM_RedefineClasses::load_new_class_versions()

由于代码较长,分为多个部分,第一部分如下


parse_stream() 这里又调用了KlassFactory::check_class_file_load_hook

    看名字就知道是个hook方法,它会调用post_class_file_load_hook。

    利用JvmtiClassFileLoadHookPoster来通知类修改器进行类的修改。进入 poster.post() 里面

消息发给所有的 jvmtienv , 最终的调用如下:

实际的消息处理者:

  eventHandlerClassFileLoadHook在收到消息后,会调用transformClassFile 

,继续跟进--->

    这里会利用JNI调用 java 层InstrumentationImpl的transform,你看,我们又绕到Java层了。

    transform 方法的调用如下:


    看到这儿,大家还记得我们开始的时候,会将我们自定义的ClassFileTransformer对象注册到TransformerManager中吗?这里终于派上用场了,TransformerManager的transform()方法会遍历它的注册数组,调用每个ClassFileTransformer对象的transform()方法,并将我们修改后的类字节码返回,返回后的字节码最终又回到了上面JVM层的transformClassFile()中,并最终交还给给class_file_load_hook 消息的发送方。


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg5MjQ1OTkwMg==&mid=2247484250&idx=1&sn=56d1b2590ed4dfa5217a750696cbdfd4&chksm=c03c8d4bf74b045d32c881bcba65cd7fc43cbbbaceaf44c0c3a57b3c91ee91b15d6d73a35687&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh