深入理解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區別


免責聲明!

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



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