JVM之类的热替换原理解读

先讲讲怎么用吧

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

第一步:写个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,利用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;
  }
}

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

原理探究

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

 void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

 void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

addTransformer()用来注册类的修改器;

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

下面让我们细细讲讲这两个函数:

3.1: addTransformer()

addTransformer的实现在InstrumentationImpl中:

    //sun.instrument.InstrumentationImpl

    public synchronized void addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
        ......
        mRetransfomableTransformerManager.addTransformer(transformer);
        ......
    }

上面代码省略了一些,可见我们的ClassFileTransformer又被添加到了TransformerManager中,让我们跟进去看看:

    //sun.instrument.TransformerManager

    public synchronized void addTransformer( ClassFileTransformer    transformer) {
        TransformerInfo[] oldList = mTransformerList;
        TransformerInfo[] newList = new TransformerInfo[oldList.length + 1];
        System.arraycopy(   oldList,
                            0,
                            newList,
                            0,
                            oldList.length);
        newList[oldList.length] = new TransformerInfo(transformer);
        mTransformerList = newList;
    }

ClassFileTransformer对象这次被放入了TransformerManager的一个数组中。

OK,注册完毕,很简单对不对?下面我们再来看下稍微复杂点的retransformClasses()吧。

3.2: retransformClasses()

这个方法的实现是个Native方法。

    private native void retransformClasses0(long var1, Class<?>[] var3);

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

// src/java.instrument/share/native/libinstrument/InstrumentationImplNativeMethods.c

JNIEXPORT void JNICALL
Java_sun_instrument_InstrumentationImpl_retransformClasses0
  (JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classes) {
    retransformClasses(jnienv, (JPLISAgent*)(intptr_t)agent, classes);
}

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

// src/hotspot/share/prims/jvmtiEnv.cpp

jvmtiError
JvmtiEnv::RetransformClasses(jint class_count, const jclass* classes) {

  int index;
  JavaThread* current_thread = JavaThread::current();
  ResourceMark rm(current_thread);

  jvmtiClassDefinition* class_definitions =
                            NEW_RESOURCE_ARRAY(jvmtiClassDefinition, class_count);

  for (index = 0; index < class_count; index++) {
    HandleMark hm(current_thread);

    jclass jcls = classes[index];
    oop k_mirror = JNIHandles::resolve_external_guard(jcls);
    ......
    Klass* klass = java_lang_Class::as_Klass(k_mirror);

    jint status = klass->jvmti_class_status();
    if (status & (JVMTI_CLASS_STATUS_ERROR)) {
      return JVMTI_ERROR_INVALID_CLASS;
    }

    InstanceKlass* ik = InstanceKlass::cast(klass);
    if (ik->get_cached_class_file_bytes() == NULL) {
      JvmtiClassFileReconstituter reconstituter(ik);
      if (reconstituter.get_error() != JVMTI_ERROR_NONE) {
        return reconstituter.get_error();
      }

      class_definitions[index].class_byte_count = (jint)reconstituter.class_file_size();
      class_definitions[index].class_bytes      = (unsigned char*)
                                                       reconstituter.class_file_bytes();
    } else {
      // it is cached, get it from the cache
      class_definitions[index].class_byte_count = ik->get_cached_class_file_len();
      class_definitions[index].class_bytes      = ik->get_cached_class_file_bytes();
    }
    class_definitions[index].klass              = jcls;
  }
  VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_retransform);
  VMThread::execute(&op);
  return (op.check_error());
} 

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

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

(2) 调用VMThread::execute(&op),进入下一步。

VMThread::execute(&op) 中会调用到 VM_RedefineClasses::doit_prologue(),最终调用到VM_RedefineClasses::load_new_class_versions():

jvmtiError VM_RedefineClasses::load_new_class_versions(TRAPS) {

    ......
    InstanceKlass* the_class = get_ik(_class_defs[i].klass);
    Symbol*  the_class_sym = the_class->name();

    ClassFileStream st((u1*)_class_defs[i].class_bytes,
                       _class_defs[i].class_byte_count,
                       "__VM_RedefineClasses__",
                       ClassFileStream::verify);

    Handle the_class_loader(THREAD, the_class->class_loader());
    Handle protection_domain(THREAD, the_class->protection_domain());
    
    state->set_class_being_redefined(the_class, _class_load_kind);

    InstanceKlass* scratch_class = SystemDictionary::parse_stream(
                                                      the_class_sym,
                                                      the_class_loader,
                                                      protection_domain,
                                                      &st,
    ......
    
}

上面这个方法调用了parse_stream(),从文件流中解析类,最终触发类的重新加载:

InstanceKlass* SystemDictionary::load_shared_class(InstanceKlass* ik,
                                                   Handle class_loader,
                                                   Handle protection_domain, TRAPS) {
    ......

    InstanceKlass* new_ik = KlassFactory::check_shared_class_file_load_hook(
        ik, class_name, class_loader, protection_domain, CHECK_NULL);

    if (new_ik != NULL) {
      return new_ik;
    }

   ......
   return ik;
}

这里又调用了KlassFactory::check_shared_class_file_load_hook(),看名字就知道是个hook方法,它会调用post_class_file_load_hook(),利用JvmtiClassFileLoadHookPoster来通知类修改器进行类的修改。

消息的处理者为:eventHandlerClassFileLoadHook():

void JNICALL
eventHandlerClassFileLoadHook(  jvmtiEnv *              jvmtienv,
                                JNIEnv *                jnienv,
                                jclass                  class_being_redefined,
                                jobject                 loader,
                                const char*             name,
                                jobject                 protectionDomain,
                                jint                    class_data_len,
                                const unsigned char*    class_data,
                                jint*                   new_class_data_len,
                                unsigned char**         new_class_data) {
    JPLISEnvironment * environment  = NULL;

    environment = getJPLISEnvironment(jvmtienv);

    /* if something is internally inconsistent (no agent), just silently return without touching the buffer */
    if ( environment != NULL ) {
        jthrowable outstandingException = preserveThrowable(jnienv);
        transformClassFile( environment->mAgent,
                            jnienv,
                            loader,
                            name,
                            class_being_redefined,
                            protectionDomain,
                            class_data_len,
                            class_data,
                            new_class_data_len,
                            new_class_data,
                            environment->mIsRetransformer);
        restoreThrowable(jnienv, outstandingException);
    }
}

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

void
transformClassFile(             JPLISAgent *            agent,
                                JNIEnv *                jnienv,
                                jobject                 loaderObject,
                                const char*             name,
                                jclass                  classBeingRedefined,
                                jobject                 protectionDomain,
                                jint                    class_data_len,
                                const unsigned char*    class_data,
                                jint*                   new_class_data_len,
                                unsigned char**         new_class_data,
                                jboolean                is_retransformer) {
            ......
            transformedBufferObject = (*jnienv)->CallObjectMethod(
                                                jnienv,
                                                agent->mInstrumentationImpl,
                                                agent->mTransform,
                                                moduleObject,
                                                loaderObject,
                                                classNameStringObject,
                                                classBeingRedefined,
                                                protectionDomain,
                                                classFileBufferObject,
                                                is_retransformer);
         ......
}

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

private byte[] transform(  Module              module,
                ClassLoader         loader,
                String              classname,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer,
                boolean             isRetransformer) {
        TransformerManager mgr = isRetransformer?
                                        mRetransfomableTransformerManager :
                                        mTransformerManager;
        // module is null when not a class load or when loading a class in an
        // unnamed module and this is the first type to be loaded in the package.
        if (module == null) {
            if (classBeingRedefined != null) {
                module = classBeingRedefined.getModule();
            } else {
                module = (loader == null) ? jdk.internal.loader.BootLoader.getUnnamedModule()
                                          : loader.getUnnamedModule();
            }
        }
        if (mgr == null) {
            return null; // no manager, no transform
        } else {
            return mgr.transform(   module,
                                    loader,
                                    classname,
                                    classBeingRedefined,
                                    protectionDomain,
                                    classfileBuffer);
        }
    }

上面主要就是调用TransformerManager的transform():

 public byte[] transform(  Module              module,
                ClassLoader         loader,
                String              classname,
                Class<?>            classBeingRedefined,
                ProtectionDomain    protectionDomain,
                byte[]              classfileBuffer) {
        boolean someoneTouchedTheBytecode = false;

        TransformerInfo[]  transformerList = getSnapshotTransformerList();

        byte[]  bufferToUse = classfileBuffer;

        // order matters, gotta run 'em in the order they were added
        for ( int x = 0; x < transformerList.length; x++ ) {
            TransformerInfo         transformerInfo = transformerList[x];
            ClassFileTransformer    transformer = transformerInfo.transformer();
            byte[]                  transformedBytes = null;

            try {
                transformedBytes = transformer.transform(   module,
                                                            loader,
                                                            classname,
                                                            classBeingRedefined,
                                                            protectionDomain,
                                                            bufferToUse);
            }
            catch (Throwable t) {
                // don't let any one transformer mess it up for the others.
                // This is where we need to put some logging. What should go here? FIXME
            }

            if ( transformedBytes != null ) {
                someoneTouchedTheBytecode = true;
                bufferToUse = transformedBytes;
            }
        }

        // if someone modified it, return the modified buffer.
        // otherwise return null to mean "no transforms occurred"
        byte [] result;
        if ( someoneTouchedTheBytecode ) {
            result = bufferToUse;
        }
        else {
            result = null;
        }

        return result;
    }

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

让我们回到消息的发送方:check_shared_class_file_load_hook()中去看看:

InstanceKlass* KlassFactory::check_shared_class_file_load_hook(
                                          InstanceKlass* ik,
                                          Symbol* class_name,
                                          Handle class_loader,
                                          Handle protection_domain, TRAPS) {
#if INCLUDE_CDS && INCLUDE_JVMTI
  assert(ik != NULL, "sanity");
  assert(ik->is_shared(), "expecting a shared class");

  if (JvmtiExport::should_post_class_file_load_hook()) {
    assert(THREAD->is_Java_thread(), "must be JavaThread");

    // Post the CFLH
    JvmtiCachedClassFileData* cached_class_file = NULL;
    JvmtiCachedClassFileData* archived_class_data = ik->get_archived_class_data();
    assert(archived_class_data != NULL, "shared class has no archived class data");
    unsigned char* ptr =
        VM_RedefineClasses::get_cached_class_file_bytes(archived_class_data);
    unsigned char* end_ptr =
        ptr + VM_RedefineClasses::get_cached_class_file_len(archived_class_data);
    unsigned char* old_ptr = ptr;
    JvmtiExport::post_class_file_load_hook(class_name,
                                           class_loader,
                                           protection_domain,
                                           &ptr,
                                           &end_ptr,
                                           &cached_class_file);
    // 这里判断类是否被修改了
    if (old_ptr != ptr) {
      ......
      // 根据返回的类字节码指针及指针范围,构造ClassFileStream
      ClassFileStream* stream = new ClassFileStream(ptr,
                                                    end_ptr - ptr,
                                                    pathname,
                                                    ClassFileStream::verify);
      // 构建新返回的类字节码解析器
      ClassFileParser parser(stream,
                             class_name,
                             loader_data,
                             protection_domain,
                             NULL,
                             NULL,
                             ClassFileParser::BROADCAST, // publicity level
                             CHECK_NULL);
      // 解析新返回的类字节码,构造出新的类实例InstanceKlass
      InstanceKlass* new_ik = parser.create_instance_klass(true /* changed_by_loadhook */,
                                                           CHECK_NULL);
      if (cached_class_file != NULL) {
        new_ik->set_cached_class_file(cached_class_file);
      }

      if (class_loader.is_null()) {
        ResourceMark rm;
        ClassLoader::add_package(class_name->as_C_string(), path_index, THREAD);
      }

      return new_ik;
    }
  }
#endif

  return NULL;
}

check_shared_class_file_load_hook()中会解析新返回的类字节码,构造出新的类new_ik,最后,基于修改后的字节码构造出来的新类new_ik 会被返回给上一层,完成类的链接等任务,最终实现了类的热替换。

总结

所谓类的热替换,本质上,就是触发了类的重新加载,并在类加载过程中,通过Hook方法,修改类的字节码,并基于修改后的字节码,重新初始化类而已。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java Agent是Arthas使用的技术,是Skywalking使用的技术,是一份十分重要的技术。 课程的稀缺性在此之前,市面上并没有针对Java Agent进行系统介绍的课程。 通过搜索引擎查找,会发现与Java Agent相关的内容大多是个人知识总结分享的内容。这些内容有如下特点:内容质量不一详略程度不一学习难度千差万别总体上来说,学习者很难有一个整体认知、系统学习的过程。 课程的设计目标 在构思课程内容时,本课程带有以下目标:课程学习梯度:从简单到复杂,让学习者有一个循序渐进的理解过程。构造完整、统一的知识体系:不是零散的知识点堆砌,而是有一个统一的贯穿始终的知识框架。具有可操作性的代码示例,不只是讲概念,更注意于实践。课程内容安排 本课程通过四章内容对Java Agent相关知识进行讲解:第一章,介绍Agent Jar的三个组成部分:Manifest、Agent ClassClassFileTransformer。第二章,介绍Agent Jar的两种启动方式:从命令行启动和使用Attach机制启动。第三章,介绍如何利用Instrumentation API来实现Agent Jar的功能。第四章,Java Agent的应用与技巧。 通过本课程的学习,让同学们更好地建立起一个完整的知识体系:  讲师介绍我叫刘森,南京师范大学研究生毕业,2015年获得信息系统项目管理师(高级),2014年获得系统集成项目管理工程师(中级)。 目前,我的课程都是围绕着“Java字节码”技术展开: 《Java Agent基础篇》是在一个运行JVM当中提供修改字节码的机会《Java ASM系列》(免费课程)是一个操作字节码的类库《Java 8 ClassFile》专注于字节码的理论知识,入选为“51CTO数字化人才证书项目认证课程” 因此,我对字节码技术有较为深入的研究和理解,大家想学习字节码的技术可以找我:字节码技术找刘森,轻松学习又省心~~~ 
JVM的类加载是由类加载器及其子类实现的。类加载器是Java运行时系统的重要组成部分,负责在运行时查找和加载类文件中的类。在JVM中,类加载器按照一定的层次结构进行组织,每个类加载器负责加载特定位置的类。其中,启动类加载器(Bootstrap ClassLoader)是负责加载存放在<JAVA_HOME>/lib目录中的核心类库,如rt.jar、resources.jar等,同时也可以加载通过-Xbootclasspath参数指定的路径中的类库。启动类加载器是用C语言编写的,随着JVM启动而加载。当JVM需要使用某个类时,它会通过类加载器查找并加载这个类。加载过程会经历连接阶段,包括验证、准备和解析。在验证阶段,JVM会确保加载的类信息符合JVM规范。在准备阶段,JVM会为类变量分配内存并设置初始值,在方法区中分配这些内存。在解析阶段,JVM会根据符号引用替换为直接引用,以便后续的使用。通过类加载器的协同工作,JVM能够在运行时动态加载类,从而实现Java的灵活性和跨平台性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [JVM 的类加载原理](https://blog.csdn.net/ChineseSoftware/article/details/119212339)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [JVM类加载器](https://blog.csdn.net/rockvine/article/details/124825354)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员-张凯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值