深入理解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中,主要流程如下
- 创建并初始化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类型
参考
本文来自博客园,作者:9418,转载请注明原文链接:https://www.cnblogs.com/yichengtech/p/15854415.html