JVM源码分析之javaagent原理完全解读--转
原文地址:http://www.infoq.com/cn/articles/javaagent-illustrated
<h1 class="p1"><span class="s1">JVMTIAgent <pre class=" language-cpp"><code class=" language-cpp">JNIEXPORT jint JNICALL <span class="token function">Agent_OnLoad<span class="token punctuation">(JavaVM <span class="token operator">vm<span class="token punctuation">,<span class="token keyword">char <span class="token operator">options<span class="token punctuation">,<span class="token keyword">void <span class="token operator">*reserved<span class="token punctuation">)<span class="token punctuation">; JNIEXPORT jint JNICALL JNIEXPORT <span class="token keyword">void JNICALL struct _JPLISEnvironment <span class="token punctuation">{ <p class="p2"><span class="s1">这里解释一下几个重要项: <span class="token function">eventHandlerClassFileLoadHook<span class="token punctuation">( jvmtiEnv <span class="token operator"> jvmtienv<span class="token punctuation">,JNIEnv <span class="token operator"> jnienv<span class="token punctuation">,jclass <span class="token class-name">class_being_redefined<span class="token punctuation">,jobject loader<span class="token punctuation">,<span class="token keyword">const <span class="token keyword">char<span class="token operator"> name<span class="token punctuation">,jobject protectionDomain<span class="token punctuation">,jint class_data_len<span class="token punctuation">,<span class="token keyword">const <span class="token keyword">unsigned <span class="token keyword">char<span class="token operator"> class_data<span class="token punctuation">,jint<span class="token operator"> new_class_data_len<span class="token punctuation">,<span class="token keyword">unsigned <span class="token keyword">char<span class="token operator"><span class="token operator">* new_class_data<span class="token punctuation">) <span class="token punctuation">{ <p class="p2"><span class="s1">先根据jvmtiEnv取得对应的JPLISEnvironment,因为上面我已经说到其实有两个JPLISEnvironment(并且有两个jvmtiEnv),其中一个是专门做retransform的,而另外一个用来做其他事情,根据不同的用途,在注册具体的ClassFileTransformer时也是分开的,对于作为retransform用的ClassFileTransformer,我们会注册到一个单独的TransformerManager里。 <p class="p2"><span class="s1">接着调用transformClassFile方法,由于函数实现比较长,这里就不贴代码了,大致意思就是调用InstrumentationImpl对象的transform方法,根据最后那个参数来决定选哪个TransformerManager里的ClassFileTransformer对象们做transform操作。 <pre class=" language-java"><code class=" language-java"><span class="token keyword">private <span class="token keyword">byte<span class="token punctuation">[<span class="token punctuation">] <span class="token function">transform<span class="token punctuation">( ClassLoader loader<span class="token punctuation">,String classname<span class="token punctuation">,Class <span class="token class-name">classBeingRedefined<span class="token punctuation">,ProtectionDomain protectionDomain<span class="token punctuation">,<span class="token keyword">byte<span class="token punctuation">[<span class="token punctuation">] classfileBuffer<span class="token punctuation">,<span class="token keyword">boolean isRetransformer<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">public <span class="token keyword">byte<span class="token punctuation">[<span class="token punctuation">]
<h1 class="p1"><span class="s1">Class Transform的实现 <p class="p2"><span class="s1">这里说的class transform其实是狭义的,主要是针对第一次类文件加载时就要求被transform的场景,在加载类文件的时候发出ClassFileLoad事件,然后交给instrumenat agent来调用javaagent里注册的ClassFileTransformer实现字节码的修改。 <h1 class="p1"><span class="s1">Class Redefine的实现 <p class="p2"><span class="s1">类重新定义,这是Instrumentation提供的基础功能之一,主要用在已经被加载过的类上,想对其进行修改,要做这件事,我们必须要知道两个东西,一个是要修改哪个类,另外一个是想将那个类修改成怎样的结构,有了这两个信息之后就可以通过InstrumentationImpl下面的redefineClasses方法操作了: <pre class=" language-java"><code class=" language-java"><span class="token keyword">public <span class="token keyword">void <span class="token function">redefineClasses<span class="token punctuation">(ClassDefinition<span class="token punctuation">[<span class="token punctuation">] definitions<span class="token punctuation">) <span class="token keyword">throws ClassNotFoundException <span class="token punctuation">{
<pre class=" language-cpp"><code class=" language-cpp">jvmtiError JvmtiEnv<span class="token operator">::<span class="token function">RedefineClasses<span class="token punctuation">(jint class_count<span class="token punctuation">,<span class="token keyword">const jvmtiClassDefinition<span class="token operator">* class_definitions<span class="token punctuation">) <span class="token punctuation">{ <span class="token comment">//TODO: add locking VM_RedefineClasses <span class="token function">op<span class="token punctuation">(class_count<span class="token punctuation">,class_definitions<span class="token punctuation">,jvmti_class_load_kind_redefine<span class="token punctuation">)<span class="token punctuation">; VMThread<span class="token operator">::<span class="token function">execute<span class="token punctuation">(<span class="token operator">&op<span class="token punctuation">)<span class="token punctuation">; <span class="token keyword">return <span class="token punctuation">(op<span class="token punctuation">.<span class="token function">check_error<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">)<span class="token punctuation">; <span class="token punctuation">} <span class="token comment">/ end RedefineClasses / <ul class="ul1"> <li class="li2"><span class="s1">挨个遍历要批量重定义的jvmtiClassDefinition <li class="li2"><span class="s1">然后读取新的字节码,如果有关注ClassFileLoadHook事件的,还会走对应的transform来对新的字节码再做修改 <li class="li2"><span class="s1">字节码解析好,创建一个klassOop对象 <li class="li2"> <span class="s1"><span class="s1">对比新老类,并要求如下: <ul class="ul2"> <li class="li2"><span class="s1">父类是同一个 <li class="li2"><span class="s1">实现的接口数也要相同,并且是相同的接口 <li class="li2"><span class="s1">类访问符必须一致 <li class="li2"><span class="s1">字段数和字段名要一致 <li class="li2"><span class="s1">新增的方法必须是private static/final的 <li class="li2"><span class="s1">可以删除修改方法
<p class="p2"><span class="s1">不过retransform的实现其实也是通过redefine的功能来实现,在类加载的时候有比较小的差别,主要体现在究竟会走哪些transform上,如果当前是做retransform的话,那将忽略那些注册到正常的TransformerManager里的ClassFileTransformer,而只会走专门为retransform而准备的TransformerManager的ClassFileTransformer,不然想象一下字节码又被无声无息改成某个中间态了。 <pre class=" language-java"><code class=" language-java"><span class="token keyword">private<span class="token operator">: <span class="token keyword">void <span class="token function">post_all_envs<span class="token punctuation">(<span class="token punctuation">) <span class="token punctuation">{
<span class="token punctuation">} <ul class="ul1"> <li class="li6"><span class="s7">获取所有已经被加载的类:<span class="s8">Class[] getAllLoadedClasses();? <li class="li6"><span class="s7">获取所有已经初始化了的类:?<span class="s8">Class[] getInitiatedClasses(ClassLoader loader);? <li class="li6"><span class="s7">获取某个对象的大小:?<span class="s8">long getObjectSize(Object objectToSize);? <li class="li6"><span class="s7">将某个jar加入到bootstrap classpath里优先其他jar被加载:?<span class="s8">void appendToBootstrapClassLoaderSearch(JarFile jarfile);? <li class="li6"><span class="s7">将某个jar加入到classpath里供appclassloard去加载:<span class="s8">void appendToSystemClassLoaderSearch(JarFile jarfile);? <li class="li6"><span class="s7">设置某些native方法的前缀,主要在找native方法的时候做规则匹配:?<span class="s8">void setNativeMethodPrefix(ClassFileTransformer transformer,String prefix)。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |