• 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() 

      主要是释放前面的步骤内存

  • 相关阅读:
    CSS:CSS 创建
    mfs-用户
    java实现扑克牌移动
    java实现扑克牌移动
    java实现扑克牌移动
    java实现纵横火柴棋
    java实现纵横火柴棋
    java实现纵横火柴棋
    java实现纵横火柴棋
    java实现纵横火柴棋
  • 原文地址:https://www.cnblogs.com/liboware/p/12493840.html
Copyright © 2020-2023  润新知