Java-技術專區-探針技術之instrutment中retransformClasses和redefineClasses


1.前提概要

  jvm的attach的方式上如何重新定義class,里面也提到了最后attach時候會調用我們自定義的agent class的agentmain方法,在Instrumentation的接口里面實際上本身提供了redfineClasses的方法

  也就是agentmain的方法只是一個調用入口,還是需要調用sun本身提供的Instrumentation 的redfineClasses的方法去替換classes 

public static void agentmain(String agentArgs, Instrumentation inst) {
  ClassDefinition def1 = new ClassDefinition(Class, classByte);
  inst.redefineClasses(new ClassDefinition[]{def1});
}

  在sun.instrument.InstrumentationImpl 中的redefineClasses 還是調用native redefineClasses0的方法

private native void redefineClasses0(long nativeAgent, ClassDefinition[] definitions) throws ClassNotFoundException;
   JNIEXPORT void JNICALL Java_sun_instrument_InstrumentationImpl_redefineClasses0
  (JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classDefinitions) {
  redefineClasses(jnienv, (JPLISAgent*)(intptr_t)agent, classDefinitions);
}

下面是redefineClasses的部分代碼

void redefineClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classDefinitions) {

if (!errorOccurred) {

  getDefinitionClassMethodID = (*jnienv)->GetMethodID( jnienv,classDefClass,"getDefinitionClass","()Ljava/lang/Class;");
  ......
}
if (!errorOccurred) {
  getDefinitionClassFileMethodID = (*jnienv)->GetMethodID( jnienv,classDefClass,"getDefinitionClassFile","()[B");
  .....
}
classDefs[i].klass = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassMethodID);
...
targetFiles[i] = (*jnienv)->CallObjectMethod(jnienv, classDef, getDefinitionClassFileMethodID);
....
classDefs[i].class_byte_count = (*jnienv)->GetArrayLength(jnienv, targetFiles[i]);
  if (!errorOccurred) {
    jvmtiError errorCode = JVMTI_ERROR_NONE;
    errorCode = (*jvmtienv)->RedefineClasses(jvmtienv, numDefs, classDefs);
    .....
  }
}

獲取調用傳遞過來的ClassDefinition 里的class對象和需要修改的字節碼,最后調用了JVMTI 的RedefineClasses的方法  

JvmtiEnv::RedefineClasses(jint class_count, const jvmtiClassDefinition* class_definitions) {
  //TODO: add locking
  VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_redefine);
  VMThread::execute(&op);
  return (op.check_error());
}

/* end RedefineClasses */

  后是交給VM Thread去執行的,為什么要交給VM Thread,修改Class內容需要進入Safepoint的點的是stop-world的操作,而VM Thread的操作都會進入Safepoint的點

RedefineClasses 分為3部分,參考jvmtiRedefineClasses.cpp

  第一是doit_prologue 這個被在JavaThread里調用主要是解析和校驗傳遞過來的bytecode生成scrash_class

  • 挨個遍歷要批量重定義的 jvmtiClassDefinition
  • 然后讀取新的字節碼,如果有關注ClassFileLoadHook事件的,還會走對應的transform來對新的字節碼再做修改
  • 字節碼解析好,創建一個klassOop對象

 對比新老類,並要求如下:

  • 父類是同一個
  • 實現的接口數也要相同,並且是相同的接口
  • 類訪問符必須一致
  • 字段數和字段名要一致
  • 新增的方法必須是private static/final的
  • 可以刪除修改方法
  • 對新類做字節碼校驗

  第二是doit() 這是在VMThread里執行的主要是替換原來的class里的內容合並常量

  • 清除原來類上的斷點
  • 清除原來類上的JIT優化
  • 將老的jmethodId更新到新類的jmethodid上
  • 將新類的常量池的hodler指向老的類
  • 將新類和老類的一些屬性做交換,比如常量池,methods,內部類
  • 初始化新的vtable和itable
  • 交換annotation的method、field、parameter
  • 遍歷所有當前類的子類,修改他們的vtable及itable

  第三是doit_epilogue() 

  主要是釋放前面的步驟內存

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM