1. 程式人生 > >JVM之類的熱替換原理解讀

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方法,修改類的位元組碼,並基於修改後的位元組碼,重新初始化類而已。