Loading

深入理解Java Instrument

Instrument简述

Instrument“插桩”是JDK5引入的特性,允许通过代理(Agent),动态的对已加载的类进行字节码修改(增强)。例如实现非侵入式监控、注入故障等。

Instrument包实现JDK的“插桩”功能,其中Instrumentation接口提供了设置ClassTransformer修改类信息的方法。

Instrumentation: 在计算机科学技术中的英文释义是插桩、植入。
instrument: 仪器(仪器是指用以检出、测量、观察、计算各物理量、物质成分、物性参数等的器具或设备。)

Instrument的基本原理

Instrument底层依赖JVMTI(JVM Tool Interface)实现。JVMTI是JVM暴露的用户扩展接口,基于事件驱动,在特定处回调用户扩展的接口,实现Agent相关的功能。

JVMTI接口及其实现类

JVMTI暴露了三个Agent相关接口如下

/* 
 * 启动参数指定了-javaagent,则启动时会回调该函数;
 */
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *options, void *reserved);

/* 
 * 通过Attach方式向目标进程发送load命令加载agent时,触发该函数回调;
 */
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved);

/* 
 * agent卸载时回调;
 */
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *vm);

JVMTI由JVMTIAgent实现,后者实际上是一类c语言动态库文件(如:librainstrument.so)。其中JavaAgent的实现类为JPLISAgent.c(Java Programming Language Instrument Services Agent)

JPLISAgent的构造如下,其中包含了操作的JVM、agent、instrumentation等关键对象,并实现了回调方法。

JPLISEnvironment有两个:

  • mNormalEnvironment处理支持Retransform的ClassFileTransformer;
  • mRetransformEnvironment处理不支持Retransform的ClassFileTransformer
struct _JPLISEnvironment {
    jvmtiEnv *              mJVMTIEnv;              /* the JVM TI environment */
    JPLISAgent *            mAgent;                 /* 响应的Agent */
    jboolean                mIsRetransformer;       /* Can-Retransform-Classes:true */
};

struct _JPLISAgent {
    JavaVM *                mJVM;                   /* handle to the JVM */
    JPLISEnvironment        mNormalEnvironment;     /* 处理transform、redefine功能 */
    JPLISEnvironment        mRetransformEnvironment;/* 仅处理retransform */
    jobject                 mInstrumentationImpl;   /* InstrumentationImpl实现类,和JavaAgent交互的对象 */
    jmethodID               mPremainCaller;         /* 指向方法sun.instrument.InstrumentationImpl#loadClassAndCallPremain,调用自定义的agent的premain */
    jmethodID               mAgentmainCaller;       /* 指向方法sun.instrument.InstrumentationImpl#loadClassAndCallAgentmain,调用自定义的agent的agentmain方法 */
    jmethodID               mTransform;             /* 指向方法sun.instrument.InstrumentationImpl#transform,执行所有classfiletransformer的transform方法 */
    jboolean                mRedefineAvailable;     /* 是否支持redefine */
    jboolean                mRedefineAdded;         /* indicates if can_redefine_classes capability has been added */
    jboolean                mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */
    jboolean                mNativeMethodPrefixAdded;     /* indicates if can_set_native_method_prefix capability has been added */
    char const *            mAgentClassName;        /* agent class name */
    char const *            mOptionsString;         /* -javaagent options string */
};

Agent_OnLoad

JPLISAgent的Agent_OnLoad接口实现在invocationAdaptor.c中,主要流程如下

  1. 创建并初始化JPLISAgent,创建了mNormalEnvironment,并对VMInit事件设置了监听(eventHandlerVMInit)
  2. eventHandlerVMInit监听中,调用processJavaStart方法,执行premain相关逻辑
    • 创建InstrumentationImpl实例
    • 移除eventHandlerVMInit监听,设置ClassFileLoadHook监听器(eventHandlerClassFileLoadHook)
    • 执行startJavaAgent方法,JNI调用premain方法
  3. 读取Premain-Class类的信息并加载
  4. 读取META-INF文件中的配置,保存相关数据到JPLISAgent中
    • 处理Can-Retransform-Classes时,会创建mRetransformEnvironment,并再设置一个ClassFileLoadHook监听器
  5. 读取启动的命令行参数

主要代码如下:

JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {
    JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;
    jint                     result     = JNI_OK;
    JPLISAgent *             agent      = NULL;

    // 1. 创建并初始化JPLISAgent最重要的逻辑
    initerror = createNewJPLISAgent(vm, &agent);
    if ( initerror == JPLIS_INIT_ERROR_NONE ) {
        int             oldLen, newLen;
        char *          jarfile;
        char *          options;
        jarAttribute*   attributes;
        char *          premainClass;
        char *          agentClass;
        char *          bootClassPath;

        /*
         * 3. 获取premain-class配置
         */
        premainClass = getAttribute(attributes, "Premain-Class");


        /*
         * 4. 解析META-INF的相关JAR配置,包括can-retransform等
         */
        convertCapabilityAtrributes(attributes, agent);
         
        // 其他代码...
    }

    // 其他代码
    return result;
}
JPLISInitializationError
createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {
    JPLISInitializationError initerror       = JPLIS_INIT_ERROR_NONE;
    jvmtiEnv *               jvmtienv        = NULL;
    jint                     jnierror        = JNI_OK;

    *agent_ptr = NULL;
    jnierror = (*vm)->GetEnv(  vm,
                               (void **) &jvmtienv,
                               JVMTI_VERSION_1_1);
    if ( jnierror != JNI_OK ) {
        initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;
    } else {
        JPLISAgent * agent = allocateJPLISAgent(jvmtienv);
        if ( agent == NULL ) {
            initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;
        } else {
            /* 1. 初始化JPLISAgent实例 */
            initerror = initializeJPLISAgent(  agent,
                                               vm,
                                               jvmtienv);
            if ( initerror == JPLIS_INIT_ERROR_NONE ) {
                *agent_ptr = agent;
            } else {
                deallocateJPLISAgent(jvmtienv, agent);
            }
        }
        // 其他代码
    }

    return initerror;
}


JPLISInitializationError
initializeJPLISAgent(   JPLISAgent *    agent,
                        JavaVM *        vm,
                        jvmtiEnv *      jvmtienv) {
    jvmtiError      jvmtierror = JVMTI_ERROR_NONE;
    jvmtiPhase      phase;
    /* 这个方法里,只创建NormalEnvironment,mIsRetransformer=false,即只创建处理unRetransform的Envrionment */
    agent->mJVM                                      = vm;
    agent->mNormalEnvironment.mJVMTIEnv              = jvmtienv;
    agent->mNormalEnvironment.mAgent                 = agent;
    agent->mNormalEnvironment.mIsRetransformer       = JNI_FALSE;
    agent->mRetransformEnvironment.mJVMTIEnv         = NULL;        /* NULL until needed */
    agent->mRetransformEnvironment.mAgent            = agent;
    agent->mRetransformEnvironment.mIsRetransformer  = JNI_FALSE;   /* 注意,unRetransformer=false */
    agent->mAgentmainCaller                          = NULL;
    agent->mInstrumentationImpl                      = NULL;
    agent->mPremainCaller                            = NULL;
    agent->mTransform                                = NULL;
    agent->mRedefineAvailable                        = JNI_FALSE;   /* assume no for now */
    agent->mRedefineAdded                            = JNI_FALSE;
    agent->mNativeMethodPrefixAvailable              = JNI_FALSE;   /* assume no for now */
    agent->mNativeMethodPrefixAdded                  = JNI_FALSE;
    agent->mAgentClassName                           = NULL;
    agent->mOptionsString                            = NULL;

    // 环境校验代码

    if ( jvmtierror == JVMTI_ERROR_NONE ) {
        jvmtiEventCallbacks callbacks;
        memset(&callbacks, 0, sizeof(callbacks));  
             
        /*
         * 1. 设置VM初始化回调函数
         */
        callbacks.VMInit = &eventHandlerVMInit;
        jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
                                                     &callbacks,
                                                     sizeof(callbacks));
                                                    
        // 其他代码
    }
    // 其他代码
    return (jvmtierror == JVMTI_ERROR_NONE)? JPLIS_INIT_ERROR_NONE : JPLIS_INIT_ERROR_FAILURE;
}

/*
 *  JVMTI VM初始化回调函数
 *
 *  加载VM时,注册VMInit handler.
 *  VMInit handler运行时, 移除VMInit handler并注册ClassFileLoadHook handler.
 */
void JNICALL
eventHandlerVMInit( jvmtiEnv *      jvmtienv,
                    JNIEnv *        jnienv,
                    jthread         thread) {
    JPLISEnvironment * environment  = NULL;
    jboolean           success      = JNI_FALSE;

    environment = getJPLISEnvironment(jvmtienv);

    /* process the premain calls on the all the JPL agents */
    if ( environment != NULL ) {
        jthrowable outstandingException = preserveThrowable(jnienv);
        /* 2. 处理premain */
        success = processJavaStart( environment->mAgent,
                                    jnienv);
        restoreThrowable(jnienv, outstandingException);
    }

    /* 其他代码 */
}
jboolean
processJavaStart(   JPLISAgent *    agent,
                    JNIEnv *        jnienv) {
    jboolean    result;

    // 其他代码

    /*
     *  2.1 创建InstrumentationImpl实例.
     */
    if ( result ) {
        result = createInstrumentationImpl(jnienv, agent);
        jplis_assert(result);
    }


    /*
     *  2.2 关闭VMInit handler, 开启ClassFileLoadHook.
     *  This way it is on before anyone registers a transformer.
     */
    if ( result ) {
        result = setLivePhaseEventHandlers(agent);
        jplis_assert(result);
    }

    /*
     *  2.3 加载Java agent, 调用premain方法.agent->mPremainCaller
     */
    if ( result ) {
        result = startJavaAgent(agent, jnienv,
                                agent->mAgentClassName, agent->mOptionsString,
                                agent->mPremainCaller);
    }
    // 其他代码
    return result;
}


/* 
 * 将VMInit handler切换为ClassFileLoadHook handler
 */
jboolean
setLivePhaseEventHandlers(  JPLISAgent * agent) {
    jvmtiEventCallbacks callbacks;
    jvmtiEnv *          jvmtienv = jvmti(agent);
    jvmtiError          jvmtierror;

    memset(&callbacks, 0, sizeof(callbacks));
    /* 设置ClassFileLoadHook事件监听函数 */
    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;

    // 其他代码
    return (jvmtierror == JVMTI_ERROR_NONE);
}

/*
 * 调用JavaAgent的Premain方法
 */
jboolean
startJavaAgent( JPLISAgent *    agent,
                JNIEnv *        jnienv,
                const char *    classname,
                const char *    optionsString,
                jmethodID       agentMainMethod) {
    jboolean    success = JNI_FALSE;
    jstring classNameObject = NULL;
    jstring optionsStringObject = NULL;

    success = commandStringIntoJavaStrings(    jnienv,
                                               classname,
                                               optionsString,
                                               &classNameObject,
                                               &optionsStringObject);

    if (success) {
        // JNI调用sun.instrument.InstrumentationImpl#loadClassAndCallPremain
        success = invokeJavaAgentMainMethod(   jnienv,
                                               agent->mInstrumentationImpl,
                                               agentMainMethod,
                                               classNameObject,
                                               optionsStringObject);
    }

    return success;
}

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 ( environment != NULL ) {
        jthrowable outstandingException = preserveThrowable(jnienv);
        /* 通过JNI执行InstrumentationImpl的classTransform方法,此时mIsRetransformer是false */
        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);
    }
}
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) {
    // 其他代码

    if ( shouldRun ) {
        // 其他代码
        if ( !errorOutstanding ) {
            jplis_assert(agent->mInstrumentationImpl != NULL);
            jplis_assert(agent->mTransform != NULL);
            
            /* JNI调用,执行transform方法 */
            transformedBufferObject = (*jnienv)->CallObjectMethod(
                                                jnienv,
                                                agent->mInstrumentationImpl,
                                                agent->mTransform,
                                                loaderObject,
                                                classNameStringObject,
                                                classBeingRedefined,
                                                protectionDomain,
                                                classFileBufferObject,
                                                is_retransformer);
            errorOutstanding = checkForAndClearThrowable(jnienv);
            jplis_assert_msg(!errorOutstanding, "transform method call failed");
        }

    // 其他代码
    }
    return;
}

JVM通过JNI调用到InstrumentationImpl的loadClassAndCallPremain等方法后,Java方法通过反射调用自定义的premain方法。

/**
 * JNI调用Premain方法
 */
private void
loadClassAndCallPremain(    String  classname,
                            String  optionsString)
        throws Throwable {
    loadClassAndStartAgent( classname, "premain", optionsString );
}


/**
 * JNI暴露的Agentmain方法
 */
private void
loadClassAndCallAgentmain(  String  classname,
                            String  optionsString)
        throws Throwable {
    loadClassAndStartAgent( classname, "agentmain", optionsString );
}

/**
 * JNI暴露的ClassFileLoadHook回调的transform方法
 */
private byte[]
transform(  ClassLoader         loader,
            String              classname,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer,
            boolean             isRetransformer) {
    // 支持retransform和不支持retransform的manager不同
    // 根据传入的参数选择manager执行transform
    TransformerManager mgr = isRetransformer?
                                    mRetransfomableTransformerManager :
                                    mTransformerManager;
    if (mgr == null) {
        return null; // no manager, no transform
    } else {
        return mgr.transform(   loader,
                                classname,
                                classBeingRedefined,
                                protectionDomain,
                                classfileBuffer);
    }
}

/**
 * 反射执行Agent的premain或agentmain方法
 */
private void
loadClassAndStartAgent( String  classname,
                        String  methodname,
                        String  optionsString)
        throws Throwable {
    ClassLoader mainAppLoader   = ClassLoader.getSystemClassLoader();
    Class<?>    javaAgentClass  = mainAppLoader.loadClass(classname);

    Method m = null;
    NoSuchMethodException firstExc = null;
    boolean twoArgAgent = false;

    // agent class必须premain或agentmain方法
    // agent class的选择优先级:父2参数 -> 父1参数 -> 子2参数 -> 子1参数
    try {
        m = javaAgentClass.getDeclaredMethod( methodname,
                             new Class<?>[] {
                                 String.class,
                                 java.lang.instrument.Instrumentation.class
                             }
                           );
        twoArgAgent = true;
    } catch (NoSuchMethodException x) {
        firstExc = x;
    }

    if (m == null) {
        // now try the declared 1-arg method
        try {
            m = javaAgentClass.getDeclaredMethod(methodname,
                                             new Class<?>[] { String.class });
        } catch (NoSuchMethodException x) {
        }
    }

    if (m == null) {
        // now try the inherited 2-arg method
        try {
            m = javaAgentClass.getMethod( methodname,
                             new Class<?>[] {
                                 String.class,
                                 java.lang.instrument.Instrumentation.class
                             }
                           );
            twoArgAgent = true;
        } catch (NoSuchMethodException x) {
        }
    }

    if (m == null) {
        // finally try the inherited 1-arg method
        try {
            m = javaAgentClass.getMethod(methodname,
                                         new Class<?>[] { String.class });
        } catch (NoSuchMethodException x) {
            throw firstExc;
        }
    }
    setAccessible(m, true);
    
    // 反射具体的方法premain或者agentmain
    if (twoArgAgent) {
        m.invoke(null, new Object[] { optionsString, this });
    } else {
        m.invoke(null, new Object[] { optionsString });
    }
    
    setAccessible(m, false);
}

解析META-INF的convertCapabilityAtrributes方法逻辑如下

void
convertCapabilityAtrributes(const jarAttribute* attributes, JPLISAgent* agent) {
    /* set redefineClasses capability */
    if (getBooleanAttribute(attributes, "Can-Redefine-Classes")) {
        addRedefineClassesCapability(agent);
    }

    /* create an environment which has the retransformClasses capability */
    if (getBooleanAttribute(attributes, "Can-Retransform-Classes")) {
        retransformableEnvironment(agent);
    }

    /* set setNativeMethodPrefix capability */
    if (getBooleanAttribute(attributes, "Can-Set-Native-Method-Prefix")) {
        addNativeMethodPrefixCapability(agent);
    }

    /* for retransformClasses testing, set capability to use original method order */
    if (getBooleanAttribute(attributes, "Can-Maintain-Original-Method-Order")) {
        addOriginalMethodOrderCapability(agent);
    }
}
/* Return the environment with the retransformation capability.
 * Create it if it doesn't exist.
 * Return NULL if it can't be created.
 */
jvmtiEnv *
retransformableEnvironment(JPLISAgent * agent) {
    jvmtiEnv *          retransformerEnv     = NULL;
    jint                jnierror             = JNI_OK;
    jvmtiCapabilities   desiredCapabilities;
    jvmtiEventCallbacks callbacks;
    jvmtiError          jvmtierror;

    // 其他代码...
    /* 设置ClassFileLoadHook监听 */
    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;

    jvmtierror = (*retransformerEnv)->SetEventCallbacks(retransformerEnv,
                                                        &callbacks,
                                                        sizeof(callbacks));
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    if (jvmtierror == JVMTI_ERROR_NONE) {
        /* 设置retransforming environment */
        agent->mRetransformEnvironment.mJVMTIEnv = retransformerEnv;
        agent->mRetransformEnvironment.mIsRetransformer = JNI_TRUE;

    // 其他代码...
    }
    return NULL;
}

Agent_OnAttach

JPLISAgent的Agent_OnAttach接口实现在invocationAdaptor.c中,与OnLoad类似,流程如下

  1. 获取JNIEnv,保证已经成功attach到Java进程
  2. 创建并初始化JPLISAgent、设置VMInit监听(不会触发了),逻辑与OnLoad相同
  3. 读取Agent-Class并加载
  4. 读取META-INFO相关配置,设置mRetransformEnvironment ClassFileLoadHook监听,逻辑与OnLoad相同
  5. 创建InstrumentationImpl实例
  6. 设置mNormaltransformEnvironment ClassFileLoadHook监听
  7. 执行AgentMain方法

代码如下

JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
    JPLISInitializationError initerror  = JPLIS_INIT_ERROR_NONE;
    jint                     result     = JNI_OK;
    JPLISAgent *             agent      = NULL;
    JNIEnv *                 jni_env    = NULL;

    /*
     * 1. 读取JNIEnv保证已经Attach成功
     */
    result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
    jplis_assert(result==JNI_OK);

    /* 2. 创建JPLISAgent实例 */
    initerror = createNewJPLISAgent(vm, &agent);
    if ( initerror == JPLIS_INIT_ERROR_NONE ) {
        int             oldLen, newLen;
        char *          jarfile;
        char *          options;
        jarAttribute*   attributes;
        char *          agentClass;
        char *          bootClassPath;
        jboolean        success;

        // 其他代码
        // 3. 读Agent-class
        agentClass = getAttribute(attributes, "Agent-Class");
        if (agentClass == NULL) {
            fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",
                jarfile);
            free(jarfile);
            if (options != NULL) free(options);
            freeAttributes(attributes);
            return AGENT_ERROR_BADJAR;
        }

        // 其他代码
        /*
         * 4. 加载META-INF配置
         */
        convertCapabilityAtrributes(attributes, agent);

        /*
         * 5. 创建java.lang.instrument.Instrumentation instance
         */
        success = createInstrumentationImpl(jni_env, agent);
        jplis_assert(success);

        /*
         *  6. 设置ClassFileLoadHook.
         */
        if (success) {
            success = setLivePhaseEventHandlers(agent);
            jplis_assert(success);
        }

        /*
         * 7. Start the agent
         */
        if (success) {
            success = startJavaAgent(agent,
                                     jni_env,
                                     agentClass,
                                     options,
                                     agent->mAgentmainCaller);
        }

        // 其他代码...
    }
    return result;
}

ClassFileTransformer

InstrumentationImpl管理了ClassFileTransformer,对class的操作依赖于ClassFileTransformer。

ClassFileLoadHook触发时会回调JNI执行sun.instrument.InstrumentationImpl#transform方法。选择对应类型的transformerManager,执行对应Transformer的transform方法。

private byte[]
transform(  ClassLoader         loader,
            String              classname,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer,
            boolean             isRetransformer) {
    // 选择不同的Transformer
    TransformerManager mgr = isRetransformer?
                                    mRetransfomableTransformerManager :
                                    mTransformerManager;
    if (mgr == null) {
        return null; // no manager, no transform
    } else {
        return mgr.transform(   loader,
                                classname,
                                classBeingRedefined,
                                protectionDomain,
                                classfileBuffer);
    }
}
public byte[]
transform(  ClassLoader         loader,
            String              classname,
            Class<?>            classBeingRedefined,
            ProtectionDomain    protectionDomain,
            byte[]              classfileBuffer) {
    boolean someoneTouchedTheBytecode = false;

    /**
     * ClassFileTransformer列表按添加顺序执行,但是整体顺序为
     */
    TransformerInfo[]  transformerList = getSnapshotTransformerList();

    byte[]  bufferToUse = classfileBuffer;

    for ( int x = 0; x < transformerList.length; x++ ) {
        TransformerInfo         transformerInfo = transformerList[x];
        ClassFileTransformer    transformer = transformerInfo.transformer();
        byte[]                  transformedBytes = null;

        try {
          // 执行transform
            transformedBytes = transformer.transform(   loader,
                                                        classname,
                                                        classBeingRedefined,
                                                        protectionDomain,
                                                        bufferToUse);
        }
        catch (Throwable t) {
        }

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

    byte [] result;
    if ( someoneTouchedTheBytecode ) {
        result = bufferToUse;
    }
    else {
        result = null;
    }

    return result;
}

执行顺序

当一个agent中有多个ClassFileTransformer时,默认按照加入列表的顺序执行,但整体的执行顺序如下

  • 不支持retransform的Transformer
  • 不支持retransform的native Transformer
  • 支持retransform的Transformer
  • 支持restransform的native Transformer

触发时机

由于类只会加载一次,理论上ClassFileLoadHook只能触发一次。但实际上有两种情况会被触发

  1. JVM启动类首次被加载时;
  2. 使用Instrument的retransformClasses、redefine指定重新载入class(用于AgentMain);

InstrumentationImpl的retransform JNI调用如下

/*
 * Class:     sun_instrument_InstrumentationImpl
 * Method:    retransformClasses0
 * Signature: ([Ljava/lang/Class;)V
 */
JNIEXPORT void JNICALL
Java_sun_instrument_InstrumentationImpl_retransformClasses0
  (JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classes) {
    retransformClasses(jnienv, (JPLISAgent*)(intptr_t)agent, classes);
}
void
retransformClasses(JNIEnv * jnienv, JPLISAgent * agent, jobjectArray classes) {
    jvmtiEnv *  retransformerEnv     = retransformableEnvironment(agent);
    
    // 其他代码
}
/* Return the environment with the retransformation capability.
 * Create it if it doesn't exist.
 * Return NULL if it can't be created.
 */
jvmtiEnv *
retransformableEnvironment(JPLISAgent * agent) {
    jvmtiEnv *          retransformerEnv     = NULL;
    jint                jnierror             = JNI_OK;
    jvmtiCapabilities   desiredCapabilities;
    jvmtiEventCallbacks callbacks;
    jvmtiError          jvmtierror;

    // 其他代码...
    
    // 设置ClassFileLoadHook监听
    callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;

    // 其他代码...
    
    jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
    if (jvmtierror == JVMTI_ERROR_NONE) {
        // 设置 the retransforming environment
        agent->mRetransformEnvironment.mJVMTIEnv = retransformerEnv;
        agent->mRetransformEnvironment.mIsRetransformer = JNI_TRUE;
        // 其他代码
    }
    return NULL;
}

通过Hotspot的源码也可以得到验证

// this entry is for class file load hook on class load, redefine and retransform
// 调用post classFileLoadHook事件的方法,只在首次加载class、redefine、retransform时执行
void JvmtiExport::post_class_file_load_hook(Symbol* h_name,
                                            Handle class_loader,
                                            Handle h_protection_domain,
                                            unsigned char **data_ptr,
                                            unsigned char **end_ptr,
                                            JvmtiCachedClassFileData **cache_ptr) {
  JvmtiClassFileLoadHookPoster poster(h_name, class_loader,
                                      h_protection_domain,
                                      data_ptr, end_ptr,
                                      cache_ptr);
  poster.post();
}
  void post() {
    post_all_envs();
    copy_modified_data();
  }
  
 private:
  void post_all_envs() {
    if (_load_kind != jvmti_class_load_kind_retransform) {
      // for class load and redefine,
      // call the non-retransformable agents
      JvmtiEnvIterator it;
      for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
        if (!env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
          // non-retransformable agents cannot retransform back,
          // so no need to cache the original class file bytes
          post_to_env(env, false);
        }
      }
    }
    JvmtiEnvIterator it;
    for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {
      // retransformable agents get all events
      if (env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
        // retransformable agents need to cache the original class file
        // bytes if changes are made via the ClassFileLoadHook
        post_to_env(env, true);
      }
    }
  }

  void post_to_env(JvmtiEnv* env, bool caching_needed) {
    unsigned char *new_data = NULL;
    jint new_len = 0;

    JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader,
                                    _h_protection_domain,
                                    _h_class_being_redefined);
    JvmtiJavaThreadEventTransition jet(_thread);
    JNIEnv* jni_env =  (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)?
                                                        NULL : jem.jni_env();
    // 调用 ClassFileLoadHook 的回调函数,触发InstrumentationImpl的transform方法
    jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook;
    if (callback != NULL) {
      (*callback)(env->jvmti_external(), jni_env,
                  jem.class_being_redefined(),
                  jem.jloader(), jem.class_name(),
                  jem.protection_domain(),
                  _curr_len, _curr_data,
                  &new_len, &new_data);
    }
    if (new_data != NULL) {
      // this agent has modified class data.
      if (caching_needed && *_cached_class_file_ptr == NULL) {
        // data has been changed by the new retransformable agent
        // and it hasn't already been cached, cache it
        JvmtiCachedClassFileData *p;
        p = (JvmtiCachedClassFileData *)os::malloc(
          offset_of(JvmtiCachedClassFileData, data) + _curr_len, mtInternal);
        if (p == NULL) {
          vm_exit_out_of_memory(offset_of(JvmtiCachedClassFileData, data) + _curr_len,
            OOM_MALLOC_ERROR,
            "unable to allocate cached copy of original class bytes");
        }
        p->length = _curr_len;
        memcpy(p->data, _curr_data, _curr_len);
        *_cached_class_file_ptr = p;
      }

      if (_curr_data != *_data_ptr) {
        // curr_data is previous agent modified class data.
        // And this has been changed by the new agent so
        // we can delete it now.
        _curr_env->Deallocate(_curr_data);
      }
      _curr_data = new_data;
      _curr_len = new_len;
      _curr_env = env;
    }
  }
};

Redefine和Retransform

Instrumentation的这两个API用于对已经加载的Class重新载入,触发ClassFileTransformer

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

处理已经被虚拟机加载的class使期再次被classFileTransformer执行transform;

retransform要求can-retransform-class配置为true。参数的Class信息为:

  • 类在加载后没有transform过,那么class为原始类文件;
  • 类在加载后transform过一次或多次,那么retransform的class字节码是上一次transform之后的结果;
  • 已经retransform过的类,不会受影响
void redefineClasses(ClassDefinition... definitions) throws  ClassNotFoundException, UnmodifiableClassException;

重新定义类信息,也会再次触发ClassFileTransformer执行transform;

功能与retransform接近,当应用场景不同:

  • redefine用与对某个类“fix-and-continue”的场景,即单独替换快速修复问题等场景;
  • 对于有多个agent或者classFileTransformer的场景,应使用retransform。redefine无法串行所有agent;

Instrument使用限制

  1. 必须是已经存在的Class,不能通过Premain或者Agentmain自定义权限的class
  2. 类转换之后的类,可以修改方法实现,但必须满足以下条件
    • 必须有相同的父类
    • 实现的接口完全相同
    • 访问控制符必须一致
    • 字段数、字段名必须一致
    • 新增或删除的方法必须时private static final类型

参考

你假笨
JDK文档-redefine和retransform区别

posted @ 2022-01-29 10:10  9418  阅读(1233)  评论(0编辑  收藏  举报