JVM之類的熱替換原理解讀
先講講怎麼用吧
一上來就說原理還是不怎麼合適的,先給大家講下這個技術怎麼用吧。但是這篇文章重點不是講怎麼用,所以我只講個大概流程。
第一步:寫個Agent類,獲取Instrumentation物件
public class MyAgent { private static Instrumentation mInstrumentation; public static void agentmain(String agentArgs, Instrumentation inst) { mInstrumentation = inst; } // 拿到Instrumentation物件後就可以利用ClassModifierTransformer來進行類的熱替換了 public static void modifyClass(Class clazz){ ClassFileTransformer transformer = new ClassModifierTransformer(); mInstrumentation.addTransformer(transformer, true); mInstrumentation.retransformClasses(new Class[]{clazz}); mInstrumentation.removeTransformer(transformer); } }
第二步:寫個ClassFileTransformer,利用Javassist等工具進行位元組碼修改
public class ClassModifierTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // 在這裡利用Javassist等工具修改類的位元組碼,返回修改後類的位元組陣列 return null; } }
目前已經有很多文章講具體使用方法了,大家可以Google下,我這裡先介紹兩篇:
原理探究
熱替換的核心就在於Instrumentation的兩個方法:
void addTransformer(ClassFileTransformer transformer, boolean canRetransform); void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
addTransformer()用來註冊類的修改器;
retransformClasses()會讓類重新載入,從而使得註冊的類修改器能夠重新修改類的位元組碼。
下面讓我們細細講講這兩個函式:
3.1: addTransformer()
addTransformer的實現在InstrumentationImpl中:
//sun.instrument.InstrumentationImpl
public synchronized void addTransformer(ClassFileTransformer transformer, boolean canRetransform) {
......
mRetransfomableTransformerManager.addTransformer(transformer);
......
}
上面程式碼省略了一些,可見我們的ClassFileTransformer又被新增到了TransformerManager中,讓我們跟進去看看:
//sun.instrument.TransformerManager
public synchronized void addTransformer( ClassFileTransformer transformer) {
TransformerInfo[] oldList = mTransformerList;
TransformerInfo[] newList = new TransformerInfo[oldList.length + 1];
System.arraycopy( oldList,
0,
newList,
0,
oldList.length);
newList[oldList.length] = new TransformerInfo(transformer);
mTransformerList = newList;
}
ClassFileTransformer物件這次被放入了TransformerManager的一個數組中。
OK,註冊完畢,很簡單對不對?下面我們再來看下稍微複雜點的retransformClasses()吧。
3.2: retransformClasses()
這個方法的實現是個Native方法。
private native void retransformClasses0(long var1, Class<?>[] var3);
很多同學看到Native方法就頭疼,不要急,Native方法也是人寫的,不過是一段文字而已。我們來看下他的具體實現吧:
// src/java.instrument/share/native/libinstrument/InstrumentationImplNativeMethods.c
JNIEXPORT void JNICALL
Java_sun_instrument_InstrumentationImpl_retransformClasses0
(JNIEnv * jnienv, jobject implThis, jlong agent, jobjectArray classes) {
retransformClasses(jnienv, (JPLISAgent*)(intptr_t)agent, classes);
}
retransformClasses()最後會呼叫到 jvmtiEnv.cpp中的RetransformClasses
// src/hotspot/share/prims/jvmtiEnv.cpp
jvmtiError
JvmtiEnv::RetransformClasses(jint class_count, const jclass* classes) {
int index;
JavaThread* current_thread = JavaThread::current();
ResourceMark rm(current_thread);
jvmtiClassDefinition* class_definitions =
NEW_RESOURCE_ARRAY(jvmtiClassDefinition, class_count);
for (index = 0; index < class_count; index++) {
HandleMark hm(current_thread);
jclass jcls = classes[index];
oop k_mirror = JNIHandles::resolve_external_guard(jcls);
......
Klass* klass = java_lang_Class::as_Klass(k_mirror);
jint status = klass->jvmti_class_status();
if (status & (JVMTI_CLASS_STATUS_ERROR)) {
return JVMTI_ERROR_INVALID_CLASS;
}
InstanceKlass* ik = InstanceKlass::cast(klass);
if (ik->get_cached_class_file_bytes() == NULL) {
JvmtiClassFileReconstituter reconstituter(ik);
if (reconstituter.get_error() != JVMTI_ERROR_NONE) {
return reconstituter.get_error();
}
class_definitions[index].class_byte_count = (jint)reconstituter.class_file_size();
class_definitions[index].class_bytes = (unsigned char*)
reconstituter.class_file_bytes();
} else {
// it is cached, get it from the cache
class_definitions[index].class_byte_count = ik->get_cached_class_file_len();
class_definitions[index].class_bytes = ik->get_cached_class_file_bytes();
}
class_definitions[index].klass = jcls;
}
VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_retransform);
VMThread::execute(&op);
return (op.check_error());
}
上面這段主要乾了兩件事:
(1) 根據java層的Class物件,找到JVM層的類例項InstanceKlass,並獲取類的位元組碼,存放在class_definitions陣列中。因為可以一次替換多個類,所以這裡加了一個迴圈體,遍歷每個要修改的類。
(2) 呼叫VMThread::execute(&op),進入下一步。
VMThread::execute(&op) 中會呼叫到 VM_RedefineClasses::doit_prologue(),最終呼叫到VM_RedefineClasses::load_new_class_versions():
jvmtiError VM_RedefineClasses::load_new_class_versions(TRAPS) {
......
InstanceKlass* the_class = get_ik(_class_defs[i].klass);
Symbol* the_class_sym = the_class->name();
ClassFileStream st((u1*)_class_defs[i].class_bytes,
_class_defs[i].class_byte_count,
"__VM_RedefineClasses__",
ClassFileStream::verify);
Handle the_class_loader(THREAD, the_class->class_loader());
Handle protection_domain(THREAD, the_class->protection_domain());
state->set_class_being_redefined(the_class, _class_load_kind);
InstanceKlass* scratch_class = SystemDictionary::parse_stream(
the_class_sym,
the_class_loader,
protection_domain,
&st,
......
}
上面這個方法呼叫了parse_stream(),從檔案流中解析類,最終觸發類的重新載入:
InstanceKlass* SystemDictionary::load_shared_class(InstanceKlass* ik,
Handle class_loader,
Handle protection_domain, TRAPS) {
......
InstanceKlass* new_ik = KlassFactory::check_shared_class_file_load_hook(
ik, class_name, class_loader, protection_domain, CHECK_NULL);
if (new_ik != NULL) {
return new_ik;
}
......
return ik;
}
這裡又呼叫了KlassFactory::check_shared_class_file_load_hook(),看名字就知道是個hook方法,它會呼叫post_class_file_load_hook(),利用JvmtiClassFileLoadHookPoster來通知類修改器進行類的修改。
訊息的處理者為:eventHandlerClassFileLoadHook():
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 something is internally inconsistent (no agent), just silently return without touching the buffer */
if ( environment != NULL ) {
jthrowable outstandingException = preserveThrowable(jnienv);
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);
}
}
eventHandlerClassFileLoadHook()在收到訊息後,會呼叫transformClassFile():
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) {
......
transformedBufferObject = (*jnienv)->CallObjectMethod(
jnienv,
agent->mInstrumentationImpl,
agent->mTransform,
moduleObject,
loaderObject,
classNameStringObject,
classBeingRedefined,
protectionDomain,
classFileBufferObject,
is_retransformer);
......
}
這裡會利用JNI呼叫 java 層InstrumentationImpl的transform(),你看,我們又繞到Java層了:
private byte[] transform( Module module,
ClassLoader loader,
String classname,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer,
boolean isRetransformer) {
TransformerManager mgr = isRetransformer?
mRetransfomableTransformerManager :
mTransformerManager;
// module is null when not a class load or when loading a class in an
// unnamed module and this is the first type to be loaded in the package.
if (module == null) {
if (classBeingRedefined != null) {
module = classBeingRedefined.getModule();
} else {
module = (loader == null) ? jdk.internal.loader.BootLoader.getUnnamedModule()
: loader.getUnnamedModule();
}
}
if (mgr == null) {
return null; // no manager, no transform
} else {
return mgr.transform( module,
loader,
classname,
classBeingRedefined,
protectionDomain,
classfileBuffer);
}
}
上面主要就是呼叫TransformerManager的transform():
public byte[] transform( Module module,
ClassLoader loader,
String classname,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
boolean someoneTouchedTheBytecode = false;
TransformerInfo[] transformerList = getSnapshotTransformerList();
byte[] bufferToUse = classfileBuffer;
// order matters, gotta run 'em in the order they were added
for ( int x = 0; x < transformerList.length; x++ ) {
TransformerInfo transformerInfo = transformerList[x];
ClassFileTransformer transformer = transformerInfo.transformer();
byte[] transformedBytes = null;
try {
transformedBytes = transformer.transform( module,
loader,
classname,
classBeingRedefined,
protectionDomain,
bufferToUse);
}
catch (Throwable t) {
// don't let any one transformer mess it up for the others.
// This is where we need to put some logging. What should go here? FIXME
}
if ( transformedBytes != null ) {
someoneTouchedTheBytecode = true;
bufferToUse = transformedBytes;
}
}
// if someone modified it, return the modified buffer.
// otherwise return null to mean "no transforms occurred"
byte [] result;
if ( someoneTouchedTheBytecode ) {
result = bufferToUse;
}
else {
result = null;
}
return result;
}
看到這兒,大家還記得我們開始的時候,會將我們自定義的ClassFileTransformer物件註冊到TransformerManager中嗎?這裡終於派上用場了,TransformerManager的transform()方法會遍歷它的註冊陣列,呼叫每個ClassFileTransformer物件的transform()方法,並將我們修改後的類位元組碼返回,返回後的位元組碼最終又回到了上面JVM層的transformClassFile()中,並最終交還給給class_file_load_hook 訊息的傳送方。
讓我們回到訊息的傳送方:check_shared_class_file_load_hook()中去看看:
InstanceKlass* KlassFactory::check_shared_class_file_load_hook(
InstanceKlass* ik,
Symbol* class_name,
Handle class_loader,
Handle protection_domain, TRAPS) {
#if INCLUDE_CDS && INCLUDE_JVMTI
assert(ik != NULL, "sanity");
assert(ik->is_shared(), "expecting a shared class");
if (JvmtiExport::should_post_class_file_load_hook()) {
assert(THREAD->is_Java_thread(), "must be JavaThread");
// Post the CFLH
JvmtiCachedClassFileData* cached_class_file = NULL;
JvmtiCachedClassFileData* archived_class_data = ik->get_archived_class_data();
assert(archived_class_data != NULL, "shared class has no archived class data");
unsigned char* ptr =
VM_RedefineClasses::get_cached_class_file_bytes(archived_class_data);
unsigned char* end_ptr =
ptr + VM_RedefineClasses::get_cached_class_file_len(archived_class_data);
unsigned char* old_ptr = ptr;
JvmtiExport::post_class_file_load_hook(class_name,
class_loader,
protection_domain,
&ptr,
&end_ptr,
&cached_class_file);
// 這裡判斷類是否被修改了
if (old_ptr != ptr) {
......
// 根據返回的類位元組碼指標及指標範圍,構造ClassFileStream
ClassFileStream* stream = new ClassFileStream(ptr,
end_ptr - ptr,
pathname,
ClassFileStream::verify);
// 構建新返回的類位元組碼解析器
ClassFileParser parser(stream,
class_name,
loader_data,
protection_domain,
NULL,
NULL,
ClassFileParser::BROADCAST, // publicity level
CHECK_NULL);
// 解析新返回的類位元組碼,構造出新的類例項InstanceKlass
InstanceKlass* new_ik = parser.create_instance_klass(true /* changed_by_loadhook */,
CHECK_NULL);
if (cached_class_file != NULL) {
new_ik->set_cached_class_file(cached_class_file);
}
if (class_loader.is_null()) {
ResourceMark rm;
ClassLoader::add_package(class_name->as_C_string(), path_index, THREAD);
}
return new_ik;
}
}
#endif
return NULL;
}
check_shared_class_file_load_hook()中會解析新返回的類位元組碼,構造出新的類new_ik,最後,基於修改後的位元組碼構造出來的新類new_ik 會被返回給上一層,完成類的連結等任務,最終實現了類的熱替換。
總結
所謂類的熱替換,本質上,就是觸發了類的重新載入,並在類載入過程中,通過Hook方法,修改類的位元組碼,並基於修改後的位元組碼,重新初始化類而已。