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中,主要流程如下
- 創建並初始化JPLISAgent,創建了mNormalEnvironment,並對VMInit事件設置了監聽(eventHandlerVMInit)
- eventHandlerVMInit監聽中,調用processJavaStart方法,執行premain相關邏輯
- 創建InstrumentationImpl實例
- 移除eventHandlerVMInit監聽,設置ClassFileLoadHook監聽器(eventHandlerClassFileLoadHook)
- 執行startJavaAgent方法,JNI調用premain方法
- 讀取Premain-Class類的信息並加載
- 讀取META-INF文件中的配置,保存相關數據到JPLISAgent中
- 處理Can-Retransform-Classes時,會創建mRetransformEnvironment,並再設置一個ClassFileLoadHook監聽器
- 讀取啟動的命令行參數
主要代碼如下:
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類似,流程如下
- 獲取JNIEnv,保證已經成功attach到Java進程
- 創建並初始化JPLISAgent、設置VMInit監聽(不會觸發了),邏輯與OnLoad相同
- 讀取Agent-Class並加載
- 讀取META-INFO相關配置,設置mRetransformEnvironment ClassFileLoadHook監聽,邏輯與OnLoad相同
- 創建InstrumentationImpl實例
- 設置mNormaltransformEnvironment ClassFileLoadHook監聽
- 執行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只能觸發一次。但實際上有兩種情況會被觸發
- JVM啟動類首次被加載時;
- 使用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使用限制
- 必須是已經存在的Class,不能通過Premain或者Agentmain自定義權限的class
- 類轉換之后的類,可以修改方法實現,但必須滿足以下條件
- 必須有相同的父類
- 實現的接口完全相同
- 訪問控制符必須一致
- 字段數、字段名必須一致
- 新增或刪除的方法必須時private static final類型