原文地址:http://www.infoq.com/cn/articles/javaagent-illustrated
文章里也会讲到JVMTIAgent(C实现的),因为javaagent的运行还是依赖于一个特殊的JVMTIAgent。
用法大致如下:
java -javaagent:myagent.jar=mode=test Test
功能如下:
- 文件之前做拦截,对字节码做修改
- 功能
- 获取所有已经加载过的类
- 获取所有已经初始化过的类(执行过clinit方法,是上面的一个子集)
- 获取某个对象的大小
- 方法的前缀,主要在查找native方法的时候做规则匹配
jvmtiEnv <span class="token operator">* jvmtienv <span class="token operator">= <span class="token function">jvmti<span class="token punctuation">(agent<span class="token punctuation">)<span class="token punctuation">;
jvmtiError jvmtierror<span class="token punctuation">;
<span class="token function">memset<span class="token punctuation">(<span class="token operator">&callbacks<span class="token punctuation">,<span class="token number">0<span class="token punctuation">,<span class="token keyword">sizeof<span class="token punctuation">(callbacks<span class="token punctuation">)<span class="token punctuation">)<span class="token punctuation">;
callbacks<span class="token punctuation">.ClassFileLoadHook <span class="token operator">= <span class="token operator">&eventHandlerClassFileLoadHook<span class="token punctuation">;
jvmtierror <span class="token operator">= <span class="token punctuation">(<span class="token operator">*jvmtienv<span class="token punctuation">)<span class="token operator">-<span class="token operator">><span class="token function">SetEventCallbacks<span class="token punctuation">( jvmtienv<span class="token punctuation">,<span class="token operator">&callbacks<span class="token punctuation">,<span class="token keyword">sizeof<span class="token punctuation">(callbacks<span class="token punctuation">)<span class="token punctuation">)<span class="token punctuation">;</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卸载时调用,不过貌似基本上很少实现它。
struct _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">这里解释一下几个重要项:
- 功能。
- 功能。
- 方法的时候注意到了有个Instrumentation参数,该参数其实就是这里的对象。
- 方法,如果agent是在启动时加载的,则该方法会被调用。
- 方法,该方法在通过attach的方式动态加载agent的时候调用。
- 方法。
- 功能,在javaagent的MANIFEST.MF里设置`Can-Redefine-Classes:true`。
- 支持native方法前缀设置,同样在javaagent的MANIFEST.MF里设置`Can-Set-Native-Method-Prefix:true`。
- 文件里定义了`Can-Retransform-Classes:true`,将会设置mRetransformEnvironment的mIsRetransformer为true。
方法里,这里简单描述下过程:
- 内容
文件之后回调时用的,这样可以对原来的字节码做修改,那这里面究竟是怎样实现的呢?
<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">{JPLISEnvironment <span class="token operator">* environment <span class="token operator">= <span class="token constant">NULL<span class="token punctuation">; environment <span class="token operator">= <span class="token function">getJPLISEnvironment<span class="token punctuation">(jvmtienv<span class="token punctuation">)<span class="token punctuation">; <span class="token comment">/* if something is internally inconsistent (no agent),just silently return without touching the buffer */ <span class="token keyword">if <span class="token punctuation">( environment <span class="token operator">!= <span class="token constant">NULL <span class="token punctuation">) <span class="token punctuation">{ jthrowable outstandingException <span class="token operator">= <span class="token function">preserveThrowable<span class="token punctuation">(jnienv<span class="token punctuation">)<span class="token punctuation">; <span class="token function">transformClassFile<span class="token punctuation">( environment<span class="token operator">-<span class="token operator">>mAgent<span class="token punctuation">,jnienv<span class="token punctuation">,loader<span class="token punctuation">,name<span class="token punctuation">,class_being_redefined<span class="token punctuation">,protectionDomain<span class="token punctuation">,class_data_len<span class="token punctuation">,class_data<span class="token punctuation">,new_class_data_len<span class="token punctuation">,new_class_data<span class="token punctuation">,environment<span class="token operator">-<span class="token operator">>mIsRetransformer<span class="token punctuation">)<span class="token punctuation">; <span class="token function">restoreThrowable<span class="token punctuation">(jnienv<span class="token punctuation">,outstandingException<span class="token punctuation">)<span class="token punctuation">; <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">{
TransformerManager mgr <span class="token operator">= isRetransformer<span class="token operator">?
mRetransfomableTransformerManager <span class="token operator">:
mTransformerManager<span class="token punctuation">;
<span class="token keyword">if <span class="token punctuation">(mgr <span class="token operator">== null<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">return null<span class="token punctuation">; <span class="token comment">// no manager,no transform
<span class="token punctuation">} <span class="token keyword">else <span class="token punctuation">{
<span class="token keyword">return mgr<span class="token punctuation">.<span class="token function">transform<span class="token punctuation">( loader<span class="token punctuation">,classname<span class="token punctuation">,classBeingRedefined<span class="token punctuation">,classfileBuffer<span class="token punctuation">)<span class="token punctuation">;
<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">]
<span class="token function">transform<span class="token punctuation">( ClassLoader loader<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 punctuation">{
<span class="token keyword">boolean someoneTouchedTheBytecode <span class="token operator">= <span class="token boolean">false<span class="token punctuation">;
TransformerInfo<span class="token punctuation">[<span class="token punctuation">] transformerList <span class="token operator">= <span class="token function">getSnapshotTransformerList<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">;
<span class="token keyword">byte<span class="token punctuation">[<span class="token punctuation">] bufferToUse <span class="token operator">= classfileBuffer<span class="token punctuation">;
<span class="token comment">// order matters,gotta run 'em in the order they were added
<span class="token keyword">for <span class="token punctuation">( <span class="token keyword">int x <span class="token operator">= <span class="token number">0<span class="token punctuation">; x <span class="token operator">< transformerList<span class="token punctuation">.length<span class="token punctuation">; x<span class="token operator">++ <span class="token punctuation">) <span class="token punctuation">{
TransformerInfo transformerInfo <span class="token operator">= transformerList<span class="token punctuation">[x<span class="token punctuation">]<span class="token punctuation">;
ClassFileTransformer transformer <span class="token operator">= transformerInfo<span class="token punctuation">.<span class="token function">transformer<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">;
<span class="token keyword">byte<span class="token punctuation">[<span class="token punctuation">] transformedBytes <span class="token operator">= null<span class="token punctuation">;
<span class="token keyword">try <span class="token punctuation">{
transformedBytes <span class="token operator">= transformer<span class="token punctuation">.<span class="token function">transform<span class="token punctuation">( loader<span class="token punctuation">,bufferToUse<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token keyword">catch <span class="token punctuation">(<span class="token class-name">Throwable t<span class="token punctuation">) <span class="token punctuation">{
<span class="token comment">// don't let any one transformer mess it up for the others.
<span class="token comment">// This is where we need to put some logging. What should go here? FIXME
<span class="token punctuation">}
<span class="token keyword">if <span class="token punctuation">( transformedBytes <span class="token operator">!= null <span class="token punctuation">) <span class="token punctuation">{
someoneTouchedTheBytecode <span class="token operator">= <span class="token boolean">true<span class="token punctuation">;
bufferToUse <span class="token operator">= transformedBytes<span class="token punctuation">;
<span class="token punctuation">}
<span class="token punctuation">}
<span class="token comment">// if someone modified it,return the modified buffer.
<span class="token comment">// otherwise return null to mean "no transforms occurred"
<span class="token keyword">byte <span class="token punctuation">[<span class="token punctuation">] result<span class="token punctuation">;
<span class="token keyword">if <span class="token punctuation">( someoneTouchedTheBytecode <span class="token punctuation">) <span class="token punctuation">{
result <span class="token operator">= bufferToUse<span class="token punctuation">;
<span class="token punctuation">}
<span class="token keyword">else <span class="token punctuation">{
result <span class="token operator">= null<span class="token punctuation">;
<span class="token punctuation">}
<span class="token keyword">return result<span class="token punctuation">;
<span class="token punctuation">}
<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">if <span class="token punctuation">(<span class="token operator">!<span class="token function">isRedefineClassesSupported<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">throw <span class="token keyword">new <span class="token class-name">UnsupportedOperationException<span class="token punctuation">(<span class="token string">"redefineClasses is not supported in this environment"<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token keyword">if <span class="token punctuation">(definitions <span class="token operator">== null<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">throw <span class="token keyword">new <span class="token class-name">NullPointerException<span class="token punctuation">(<span class="token string">"null passed as 'definitions' in redefineClasses"<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token keyword">for <span class="token punctuation">(<span class="token keyword">int i <span class="token operator">= <span class="token number">0<span class="token punctuation">; i <span class="token operator">< definitions<span class="token punctuation">.length<span class="token punctuation">; <span class="token operator">++i<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">if <span class="token punctuation">(definitions<span class="token punctuation">[i<span class="token punctuation">] <span class="token operator">== null<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">throw <span class="token keyword">new <span class="token class-name">NullPointerException<span class="token punctuation">(<span class="token string">"element of 'definitions' is null in redefineClasses"<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token punctuation">}
<span class="token keyword">if <span class="token punctuation">(definitions<span class="token punctuation">.length <span class="token operator">== <span class="token number">0<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">return<span class="token punctuation">; <span class="token comment">// short-circuit if there are no changes requested
<span class="token punctuation">}
<span class="token function">redefineClasses0<span class="token punctuation">(mNativeAgent<span class="token punctuation">,definitions<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<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_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 /
<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">可以删除修改方法
内容,相当于只更新了指针指向的内容,并没有更新指针,避免了遍历大量已有类对象对它们进行更新所带来的开销。
方法参数看猜到应该是做回滚操作,因为我们只指定了class:
<span class="token keyword">if <span class="token punctuation">(<span class="token operator">!<span class="token function">isRetransformClassesSupported<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">throw <span class="token keyword">new <span class="token class-name">UnsupportedOperationException<span class="token punctuation">( <span class="token string">"retransformClasses is not supported in this environment"<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token function">retransformClasses0<span class="token punctuation">(mNativeAgent<span class="token punctuation">,classes<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}</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">if <span class="token punctuation">(_load_kind <span class="token operator">!= jvmti_class_load_kind_retransform<span class="token punctuation">) <span class="token punctuation">{
<span class="token comment">// for class load and redefine,<span class="token comment">// call the non-retransformable agents
JvmtiEnvIterator it<span class="token punctuation">;
<span class="token keyword">for <span class="token punctuation">(JvmtiEnv<span class="token operator">* env <span class="token operator">= it<span class="token punctuation">.<span class="token function">first<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">; env <span class="token operator">!= NULL<span class="token punctuation">; env <span class="token operator">= it<span class="token punctuation">.<span class="token function">next<span class="token punctuation">(env<span class="token punctuation">)<span class="token punctuation">) <span class="token punctuation">{
<span class="token keyword">if <span class="token punctuation">(<span class="token operator">!env<span class="token operator">-<span class="token operator">><span class="token function">is_retransformable<span class="token punctuation">(<span class="token punctuation">) <span class="token operator">&& env<span class="token operator">-<span class="token operator">><span class="token function">is_enabled<span class="token punctuation">(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK<span class="token punctuation">)<span class="token punctuation">) <span class="token punctuation">{
<span class="token comment">// non-retransformable agents cannot retransform back,<span class="token comment">// so no need to cache the original class file bytes
<span class="token function">post_to_env<span class="token punctuation">(env<span class="token punctuation">,<span class="token boolean">false<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token punctuation">}
<span class="token punctuation">}
JvmtiEnvIterator it<span class="token punctuation">;
<span class="token keyword">for <span class="token punctuation">(JvmtiEnv<span class="token operator">* env <span class="token operator">= it<span class="token punctuation">.<span class="token function">first<span class="token punctuation">(<span class="token punctuation">)<span class="token punctuation">; env <span class="token operator">!= NULL<span class="token punctuation">; env <span class="token operator">= it<span class="token punctuation">.<span class="token function">next<span class="token punctuation">(env<span class="token punctuation">)<span class="token punctuation">) <span class="token punctuation">{
<span class="token comment">// retransformable agents get all events
<span class="token keyword">if <span class="token punctuation">(env<span class="token operator">-<span class="token operator">><span class="token function">is_retransformable<span class="token punctuation">(<span class="token punctuation">) <span class="token operator">&& env<span class="token operator">-<span class="token operator">><span class="token function">is_enabled<span class="token punctuation">(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK<span class="token punctuation">)<span class="token punctuation">) <span class="token punctuation">{
<span class="token comment">// retransformable agents need to cache the original class file
<span class="token comment">// bytes if changes are made via the ClassFileLoadHook
<span class="token function">post_to_env<span class="token punctuation">(env<span class="token punctuation">,<span class="token boolean">true<span class="token punctuation">)<span class="token punctuation">;
<span class="token punctuation">}
<span class="token punctuation">}
<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)。 原文链接:https://www.f2er.com/jvm/72280.html