微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JVM源码分析之javaagent原理完全解读--转

原文地址:http://www.infoq.com/cn/articles/javaagent-illustrated

文章里也会讲到jvmtiAgent(C实现的),因为javaagent的运行还是依赖于一个特殊的jvmtiAgent。

用法大致如下:

java -javaagent:myagent.jar=mode=test Test

功能如下:

    文件之前做拦截,对字节码做修改
  • 功能
      获取所有已经加载过的类
    • 获取所有已经初始化过的类(执行过clinit方法,是上面的一个子集)
    • 获取某个对象的大小
    • sspath里作为高优先级被bootstrapClassloader加载
    • sspath里供AppClassloard去加载
    • 方法的前缀,主要在查找native方法的时候做规则匹配

jvmti

jvmtiEnv <span class="token operator"&gt;*          jvmtienv <span class="token operator"&gt;= <span class="token function"&gt;jvmti<span class="token punctuation"&gt;(agent<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

jvmtiError          jvmtierror<span class="token punctuation"&gt;;

<span class="token function"&gt;memset<span class="token punctuation"&gt;(<span class="token operator"&gt;&amp;callbacks<span class="token punctuation"&gt;,<span class="token number"&gt;0<span class="token punctuation"&gt;,<span class="token keyword"&gt;sizeof<span class="token punctuation"&gt;(callbacks<span class="token punctuation"&gt;)<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

callbacks<span class="token punctuation"&gt;.ClassFileLoadHook <span class="token operator"&gt;= <span class="token operator"&gt;&amp;eventHandlerClassFileLoadHook<span class="token punctuation"&gt;;

jvmtierror <span class="token operator"&gt;= <span class="token punctuation"&gt;(<span class="token operator"&gt;*jvmtienv<span class="token punctuation"&gt;)<span class="token operator"&gt;-<span class="token operator"&gt;><span class="token function"&gt;SetEventCallbacks<span class="token punctuation"&gt;( jvmtienv<span class="token punctuation"&gt;,<span class="token operator"&gt;&amp;callbacks<span class="token punctuation"&gt;,<span class="token keyword"&gt;sizeof<span class="token punctuation"&gt;(callbacks<span class="token punctuation"&gt;)<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>

<h1 class="p1"><span class="s1">JVMTIAgent
<p class="p2"><span class="s1">JVMTIAgent其实就是一个动态库,利用JVMTI暴露出来的一些接口来干一些我们想做、但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分,它一般会实现如下的一个或者多个函数:


<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
<span class="token function">Agent_OnAttach<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 <span class="token keyword">void JNICALL
<span class="token function">Agent_OnUnload<span class="token punctuation">(JavaVM <span class="token operator">*vm<span class="token punctuation">)<span class="token punctuation">;
<ul class="ul1">
<li class="li2"><span class="s4">Agent_OnLoad<span class="s1">函数,如果agent是在启动时加载的,也就是在vm参数里通过-agentlib来指定的,那在启动过程中就会去执行这个agent里的<span class="s4">Agent_OnLoad<span class="s1">函数。
<li class="li2"><span class="s4">Agent_OnAttach<span class="s1">函数,如果agent不是在启动时加载的,而是我们先attach到目标进程上,然后给对应的目标进程发送load命令来加载,则在加载过程中会调用<span class="s4">Agent_OnAttach<span class="s1">函数。
<li class="li2"><span class="s4">Agent_OnUnload<span class="s1">函数,在agent卸载时调用,不过貌似基本上很少实现它。

jplISAgent jplISEnvironment        mnormalEnvironmentjplISEnvironment        mRetransformEnvironmentredefineAvailableredefine" */
    jboolean                mredefineAddedredefine_classes capability has been added */
    jboolean                mNativeMethodPrefixAvailablesstringstruct _jplISEnvironment <span class="token punctuation">{
jvmtiEnv <span class="token operator"> mjvmtiEnv<span class="token punctuation">; <span class="token comment">/ the JVM TI environment /
jplISAgent <span class="token operator">
mAgent<span class="token punctuation">; <span class="token comment">/ corresponding agent /
jboolean mIsRetransformer<span class="token punctuation">; <span class="token comment">/ indicates if special environment /
<span class="token punctuation">}<span class="token punctuation">;

<p class="p2"><span class="s1">这里解释一下几个重要项:

  • normalEnvironment:主要提供正常的类transform及redefine功能
  • 功能。
  • 方法的时候注意到了有个Instrumentation参数,该参数其实就是这里的对象。
  • 方法,如果agent是在启动时加载的,则该方法会被调用
  • 方法,该方法在通过attach的方式动态加载agent的时候调用
  • 方法。
  • sstring:传给agent的一些参数。
  • redefineAvailable:是否开启了redefine功能,在javaagent的MANIFEST.MF里设置`Can-redefine-Classes:true`。
  • 支持native方法前缀设置,同样在javaagent的MANIFEST.MF里设置`Can-Set-Native-Method-Prefix:true`。
  • 文件里定义了`Can-Retransform-Classes:true`,将会设置mRetransformEnvironment的mIsRetransformer为true。

方法里,这里简单描述下过程:

  • jplISAgent
    • 调用InstrumentationImpl的`loadClassAndCallPremain`方法在这方法里会调用javaagent里MANIFEST.MF里指定的`Premain-Class`类的premain方法
  • jplISAgent里的一些内容


    jplISAgent
  • 调用InstrumentationImpl的方法,在这方法里会调用javaagent里MANIFEST.MF里指定的方法

一个jvmti事件——文件之后回调时用的,这样可以对原来的字节码做修改,那这里面究竟是怎样实现的呢?

<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">classbeingredefined<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">{

<a href="/tag/jpl/" target="_blank" class="keywords">jpl</a>ISEnvironment <span class="token operator"&gt;* environment  <span class="token operator"&gt;= <span class="token constant"&gt;NULL<span class="token punctuation"&gt;;

environment <span class="token operator"&gt;= <span class="token function"&gt;get<a href="/tag/jpl/" target="_blank" class="keywords">jpl</a>ISEnvironment<span class="token punctuation"&gt;(<a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>env<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

<span class="token comment"&gt;/* if something is internally inconsistent (no agent),just silently return without touching the buffer */

<span class="token keyword"&gt;if <span class="token punctuation"&gt;( environment <span class="token operator"&gt;!= <span class="token constant"&gt;NULL <span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

    jthrowable outstandingException <span class="token operator"&gt;= <span class="token function"&gt;preserveThrowable<span class="token punctuation"&gt;(<a href="/tag/jnienv/" target="_blank" class="keywords">jnienv</a><span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
    <span class="token function"&gt;transformClassFile<span class="token punctuation"&gt;( environment<span class="token operator"&gt;-<span class="token operator"&gt;>mAgent<span class="token punctuation"&gt;,<a href="/tag/jnienv/" target="_blank" class="keywords">jnienv</a><span class="token punctuation"&gt;,loader<span class="token punctuation"&gt;,name<span class="token punctuation"&gt;,class_being_<a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>d<span class="token punctuation"&gt;,protectionDomain<span class="token punctuation"&gt;,class_data_len<span class="token punctuation"&gt;,class_data<span class="token punctuation"&gt;,new_class_data_len<span class="token punctuation"&gt;,new_class_data<span class="token punctuation"&gt;,environment<span class="token operator"&gt;-<span class="token operator"&gt;>mIsRetransformer<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

    <span class="token function"&gt;restoreThrowable<span class="token punctuation"&gt;(<a href="/tag/jnienv/" target="_blank" class="keywords">jnienv</a><span class="token punctuation"&gt;,outstandingException<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
<span class="token punctuation"&gt;}

<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">{

    TransformerManager mgr <span class="token operator"&gt;= isRetransformer<span class="token operator"&gt;?

                                    mRetransfomableTransformerManager <span class="token operator"&gt;:
                                    mTransformerManager<span class="token punctuation"&gt;;

    <span class="token keyword"&gt;if <span class="token punctuation"&gt;(mgr <span class="token operator"&gt;== null<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

        <span class="token keyword"&gt;return null<span class="token punctuation"&gt;; <span class="token comment"&gt;// no manager,no transform

    <span class="token punctuation"&gt;} <span class="token keyword"&gt;else <span class="token punctuation"&gt;{

        <span class="token keyword"&gt;return mgr<span class="token punctuation"&gt;.<span class="token function"&gt;transform<span class="token punctuation"&gt;(   loader<span class="token punctuation"&gt;,classname<span class="token punctuation"&gt;,classBeing<a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>d<span class="token punctuation"&gt;,class<a href="/tag/filebuf/" target="_blank" class="keywords">filebuf</a>fer<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

    <span class="token punctuation"&gt;}

<span class="token punctuation"&gt;}

<span class="token keyword">public <span class="token keyword">byte<span class="token punctuation">[<span class="token punctuation">]

<span class="token function"&gt;transform<span class="token punctuation"&gt;(  ClassLoader         loader<span class="token punctuation"&gt;,<span class="token keyword"&gt;byte<span class="token punctuation"&gt;[<span class="token punctuation"&gt;]              class<a href="/tag/filebuf/" target="_blank" class="keywords">filebuf</a>fer<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

    <span class="token keyword"&gt;boolean someon<a href="/tag/eto/" target="_blank" class="keywords">eto</a>uchedTheBytecode <span class="token operator"&gt;= <span class="token boolean"&gt;false<span class="token punctuation"&gt;;
    TransformerInfo<span class="token punctuation"&gt;[<span class="token punctuation"&gt;]  transformerList <span class="token operator"&gt;= <span class="token function"&gt;getSnapsh<a href="/tag/ott/" target="_blank" class="keywords">ott</a>ransformerList<span class="token punctuation"&gt;(<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
    <span class="token keyword"&gt;byte<span class="token punctuation"&gt;[<span class="token punctuation"&gt;]  bufferToUse <span class="token operator"&gt;= class<a href="/tag/filebuf/" target="_blank" class="keywords">filebuf</a>fer<span class="token punctuation"&gt;;

    <span class="token comment"&gt;// order matters,g<a href="/tag/ott/" target="_blank" class="keywords">ott</a>a run 'em in the order t<a href="/tag/hey/" target="_blank" class="keywords">hey</a> were added

    <span class="token keyword"&gt;for <span class="token punctuation"&gt;( <span class="token keyword"&gt;int x <span class="token operator"&gt;= <span class="token number"&gt;0<span class="token punctuation"&gt;; x <span class="token operator"&gt;< transformerList<span class="token punctuation"&gt;.length<span class="token punctuation"&gt;; x<span class="token operator"&gt;++ <span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

        TransformerInfo         transformerInfo <span class="token operator"&gt;= transformerList<span class="token punctuation"&gt;[x<span class="token punctuation"&gt;]<span class="token punctuation"&gt;;
        ClassFileTransformer    transformer <span class="token operator"&gt;= transformerInfo<span class="token punctuation"&gt;.<span class="token function"&gt;transformer<span class="token punctuation"&gt;(<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
        <span class="token keyword"&gt;byte<span class="token punctuation"&gt;[<span class="token punctuation"&gt;]                  transformedBytes <span class="token operator"&gt;= null<span class="token punctuation"&gt;;

        <span class="token keyword"&gt;try <span class="token punctuation"&gt;{

            transformedBytes <span class="token operator"&gt;= transformer<span class="token punctuation"&gt;.<span class="token function"&gt;transform<span class="token punctuation"&gt;(   loader<span class="token punctuation"&gt;,bufferToUse<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

        <span class="token punctuation"&gt;}

        <span class="token keyword"&gt;catch <span class="token punctuation"&gt;(<span class="token class-name"&gt;Throwable t<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

            <span class="token comment"&gt;// don't let any one transformer mess it up for the others.
            <span class="token comment"&gt;// This is where we need to put some logging. What should go here? FIXME

        <span class="token punctuation"&gt;}


        <span class="token keyword"&gt;if <span class="token punctuation"&gt;( transformedBytes <span class="token operator"&gt;!= null <span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
            someon<a href="/tag/eto/" target="_blank" class="keywords">eto</a>uchedTheBytecode <span class="token operator"&gt;= <span class="token boolean"&gt;true<span class="token punctuation"&gt;;
            bufferToUse <span class="token operator"&gt;= transformedBytes<span class="token punctuation"&gt;;
        <span class="token punctuation"&gt;}

    <span class="token punctuation"&gt;}


    <span class="token comment"&gt;// if someone modified it,return the modified buffer.
    <span class="token comment"&gt;// otherwise return null to mean "no transforms occurred"

    <span class="token keyword"&gt;byte <span class="token punctuation"&gt;[<span class="token punctuation"&gt;] result<span class="token punctuation"&gt;;

    <span class="token keyword"&gt;if <span class="token punctuation"&gt;( someon<a href="/tag/eto/" target="_blank" class="keywords">eto</a>uchedTheBytecode <span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
        result <span class="token operator"&gt;= bufferToUse<span class="token punctuation"&gt;;
    <span class="token punctuation"&gt;}
    <span class="token keyword"&gt;else <span class="token punctuation"&gt;{
        result <span class="token operator"&gt;= null<span class="token punctuation"&gt;;
    <span class="token punctuation"&gt;}

    <span class="token keyword"&gt;return result<span class="token punctuation"&gt;;

<span class="token punctuation"&gt;}   


<p class="p3">以上是最终调到的java代码,可以看到已经调用到我们自己编写的javaagent代码里了,我们一般是实现一个ClassFileTransformer类,然后创建一个对象注册到对应的TransformerManager里。


<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">{

    <span class="token keyword"&gt;if <span class="token punctuation"&gt;(<span class="token operator"&gt;!<span class="token function"&gt;is<a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>ClassesSupported<span class="token punctuation"&gt;(<span class="token punctuation"&gt;)<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

        <span class="token keyword"&gt;throw <span class="token keyword"&gt;new <span class="token class-name"&gt;UnsupportedOperationException<span class="token punctuation"&gt;(<span class="token string"&gt;"<a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>Classes is not supported in this environment"<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

    <span class="token punctuation"&gt;}

    <span class="token keyword"&gt;if <span class="token punctuation"&gt;(de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns <span class="token operator"&gt;== null<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

        <span class="token keyword"&gt;throw <span class="token keyword"&gt;new <span class="token class-name"&gt;NullPointerException<span class="token punctuation"&gt;(<span class="token string"&gt;"null passed as 'de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns' in <a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>Classes"<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

    <span class="token punctuation"&gt;}

    <span class="token keyword"&gt;for <span class="token punctuation"&gt;(<span class="token keyword"&gt;int i <span class="token operator"&gt;= <span class="token number"&gt;0<span class="token punctuation"&gt;; i <span class="token operator"&gt;< de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns<span class="token punctuation"&gt;.length<span class="token punctuation"&gt;; <span class="token operator"&gt;++i<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

        <span class="token keyword"&gt;if <span class="token punctuation"&gt;(de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns<span class="token punctuation"&gt;[i<span class="token punctuation"&gt;] <span class="token operator"&gt;== null<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

            <span class="token keyword"&gt;throw <span class="token keyword"&gt;new <span class="token class-name"&gt;NullPointerException<span class="token punctuation"&gt;(<span class="token string"&gt;"element of 'de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns' is null in <a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>Classes"<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

        <span class="token punctuation"&gt;}

    <span class="token punctuation"&gt;}

    <span class="token keyword"&gt;if <span class="token punctuation"&gt;(de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns<span class="token punctuation"&gt;.length <span class="token operator"&gt;== <span class="token number"&gt;0<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{

        <span class="token keyword"&gt;return<span class="token punctuation"&gt;; <span class="token comment"&gt;// short-circuit if there are no changes requested

    <span class="token punctuation"&gt;}


    <span class="token function"&gt;<a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>Classes0<span class="token punctuation"&gt;(mNativeAgent<span class="token punctuation"&gt;,de<a href="/tag/Finitio/" target="_blank" class="keywords">Finitio</a>ns<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;

<span class="token punctuation"&gt;}


<p class="p3"><span class="s1">在JVM里对应的实现是创建一个<span class="s4">VM_redefineClasses<span class="s1">的<span class="s4">VM_Operation<span class="s1">,注意执行它的时候会stop-the-world:


<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_loadkindredefine<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 /
<p class="p2"><span class="s1">这个过程我尽量用语言来描述清楚,不详细贴代码了,因为代码量实在有点大:


<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">可以删除修改方法

  • 方法匹配的方法的jmethodId做更新,将老的jmethodId更新到新的method上
  • 属性做交换,比如常量池,methods,内部类
  • aramenter
  • 修改他们的vtable及itable
  • 内容,相当于只更新了指针指向的内容,并没有更新指针,避免了遍历大量已有类对象对它们进行更新所带来的开销。

    一个前提,那就是javaagent已经要求要有retransform的能力了:

      代码回滚到transform之后的代码
    • 代码回滚到最原始的类文件里的字节码
    • redefine(比如被另外一个agent做过),但是接下来加载一个新的agent要求有retransform的能力了,然后对类做redefine的动作,那么retransform的时候会将代码回滚到上一个agent最后一次做redefine后的字节码

    方法参数看猜到应该是做回滚操作,因为我们只指定了class:

        <span class="token keyword"&gt;if <span class="token punctuation"&gt;(<span class="token operator"&gt;!<span class="token function"&gt;isRetransformClassesSupported<span class="token punctuation"&gt;(<span class="token punctuation"&gt;)<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
    
            <span class="token keyword"&gt;throw <span class="token keyword"&gt;new <span class="token class-name"&gt;UnsupportedOperationException<span class="token punctuation"&gt;( <span class="token string"&gt;"retransformClasses is not supported in this environment"<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
    
        <span class="token punctuation"&gt;}
    
        <span class="token function"&gt;retransformClasses0<span class="token punctuation"&gt;(mNativeAgent<span class="token punctuation"&gt;,classes<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
    
    <span class="token punctuation"&gt;}</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>

    <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 keyword"&gt;if <span class="token punctuation"&gt;(_load_kind <span class="token operator"&gt;!= <a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>_class_load_kind_retransform<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
    
      <span class="token comment"&gt;// for class load and <a href="/tag/redefine/" target="_blank" class="keywords">redefine</a>,<span class="token comment"&gt;// call the non-retransformable agents
    
      <a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>EnvI<a href="/tag/tera/" target="_blank" class="keywords">tera</a>tor it<span class="token punctuation"&gt;;
    
      <span class="token keyword"&gt;for <span class="token punctuation"&gt;(<a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>Env<span class="token operator"&gt;* env <span class="token operator"&gt;= it<span class="token punctuation"&gt;.<span class="token function"&gt;f<a href="/tag/irs/" target="_blank" class="keywords">irs</a>t<span class="token punctuation"&gt;(<span class="token punctuation"&gt;)<span class="token punctuation"&gt;; env <span class="token operator"&gt;!= NULL<span class="token punctuation"&gt;; env <span class="token operator"&gt;= it<span class="token punctuation"&gt;.<span class="token function"&gt;next<span class="token punctuation"&gt;(env<span class="token punctuation"&gt;)<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
    
        <span class="token keyword"&gt;if <span class="token punctuation"&gt;(<span class="token operator"&gt;!env<span class="token operator"&gt;-<span class="token operator"&gt;><span class="token function"&gt;is_retransformable<span class="token punctuation"&gt;(<span class="token punctuation"&gt;) <span class="token operator"&gt;&amp;&amp; env<span class="token operator"&gt;-<span class="token operator"&gt;><span class="token function"&gt;is_enabled<span class="token punctuation"&gt;(<a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>_EVENT_CLASS_FILE_LOAD_HOOK<span class="token punctuation"&gt;)<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
    
          <span class="token comment"&gt;// non-retransformable agents cannot retransform back,<span class="token comment"&gt;// so no need to cache the original class file bytes
    
          <span class="token function"&gt;post_to_env<span class="token punctuation"&gt;(env<span class="token punctuation"&gt;,<span class="token boolean"&gt;false<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
    
        <span class="token punctuation"&gt;}
    
      <span class="token punctuation"&gt;}
    
    <span class="token punctuation"&gt;}
    
    <a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>EnvI<a href="/tag/tera/" target="_blank" class="keywords">tera</a>tor it<span class="token punctuation"&gt;;
    
    <span class="token keyword"&gt;for <span class="token punctuation"&gt;(<a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>Env<span class="token operator"&gt;* env <span class="token operator"&gt;= it<span class="token punctuation"&gt;.<span class="token function"&gt;f<a href="/tag/irs/" target="_blank" class="keywords">irs</a>t<span class="token punctuation"&gt;(<span class="token punctuation"&gt;)<span class="token punctuation"&gt;; env <span class="token operator"&gt;!= NULL<span class="token punctuation"&gt;; env <span class="token operator"&gt;= it<span class="token punctuation"&gt;.<span class="token function"&gt;next<span class="token punctuation"&gt;(env<span class="token punctuation"&gt;)<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
    
      <span class="token comment"&gt;// retransformable agents get all events
    
      <span class="token keyword"&gt;if <span class="token punctuation"&gt;(env<span class="token operator"&gt;-<span class="token operator"&gt;><span class="token function"&gt;is_retransformable<span class="token punctuation"&gt;(<span class="token punctuation"&gt;) <span class="token operator"&gt;&amp;&amp; env<span class="token operator"&gt;-<span class="token operator"&gt;><span class="token function"&gt;is_enabled<span class="token punctuation"&gt;(<a href="/tag/jvmti/" target="_blank" class="keywords">jvmti</a>_EVENT_CLASS_FILE_LOAD_HOOK<span class="token punctuation"&gt;)<span class="token punctuation"&gt;) <span class="token punctuation"&gt;{
    
        <span class="token comment"&gt;// retransformable agents need to cache the original class file
    
        <span class="token comment"&gt;// bytes if changes are made via the ClassFileLoadHook
    
        <span class="token function"&gt;post_to_env<span class="token punctuation"&gt;(env<span class="token punctuation"&gt;,<span class="token boolean"&gt;true<span class="token punctuation"&gt;)<span class="token punctuation"&gt;;
    
      <span class="token punctuation"&gt;}
    
    <span class="token punctuation"&gt;}

    <span class="token punctuation">}

    <h1 class="p3">javaagent的其他小众功能
    <p class="p2"><span class="s1">javaagent除了做字节码上面的修改之外,其实还有一些小功能,有时候还是挺有用的


    <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)。

    版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

    相关推荐