在上篇博客(http://blog.csdn.net/raintungli/article/details/51646556)中提到了在on_attach的方式上如何从新定义class,里面也提到了最后attach时候会调用咱们自定义的agent class的agentmain方法,在Instrumentation的接口里面实际上自己提供了redfineClasses的方法,也就是agentmain的方法只是一个调用入口,仍是须要调用sun自己提供的Instrumentation 的redfineClasses的方法去替换classes java
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的方法jvm
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的方法spa
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.net
第一是doit_prologuecode
这个被在JavaThread里调用主要是解析和校验传递过来的bytecode生成scrash_class 挨个遍历要批量重定义的jvmtiClassDefinition 而后读取新的字节码,若是有关注ClassFileLoadHook事件的,还会走对应的transform来对新的字节码再作修改 字节码解析好,建立一个klassOop对象
对比新老类,并要求以下:orm
父类是同一个 实现的接口数也要相同,而且是相同的接口 类访问符必须一致 字段数和字段名要一致 新增的方法必须是private static/final的 能够删除修改方法 对新类作字节码校验
第二是doit() 这是在VMThread里执行的主要是替换原来的class里的内容对象
合并常量 清除原来类上的断点 清除原来类上的JIT优化 将老的jmethodId更新到新类的jmethodid上 将新类的常量池的hodler指向老的类 将新类和老类的一些属性作交换,好比常量池,methods,内部类 初始化新的vtable和itable 交换annotation的method、field、parameter 遍历全部当前类的子类,修改他们的vtable及itable
第三是doit_epilogue()
blog
主要是释放前面的步骤内存接口