繁体   English   中英

如何加快运行时 Java 代码检测?

[英]How to speed up runtime Java code instrumentation?

我制作了一个 Java 代理,它在运行时附加到 JVM 并检测所有加载的项目类并插入一些日志记录语句。 总共有 11k 个班级。 我测量了我的ClassFileTransformertransform方法所花费的总时间,它是 3 秒。 但是整个检测过程的持续时间大约需要 30 秒。 这就是我重新转换课程的方式:

 instrumentation.retransformClasses(myClassesArray);

我假设 JVM 占用了大部分时间来重新加载更改的类。 那正确吗? 如何加快检测过程?

更新
当我的代理人被附加时,

instrumentation.addTransformer(new MyTransfomer(), true);
instrumentation.retransformClasses(retransformClassArray);

只调用一次

然后MyTransfomer class 检测类并测量检测的总持续时间:


public class MyTransfomer implements ClassFileTransformer {
private long total = 0;
private long min = ..., max = ...;

public final byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) {
   long s = System.currentTimeMillis();
   if(s < min) min = s;
   if(s > max) max = s;
   byte[] transformed = this.transformInner(loader, className, classFileBuffer);

   this.total += System.currentTimeMillis() - s;
   
   return transformed;
  }
}

在检测所有类之后(从初始数组)(全局缓存跟踪检测的类) total打印出来,大约是 3 秒。 max-min约为 30 秒。

更新 2:

查看堆栈跟踪后会发生以下情况:我调用

instrumentation.retransformClasses(retransformClassArray);

它调用本机方法retransformClasses0() After some time(!) the JVM calls the transform() method of the sun.instrument.InstrumentationImpl class(but this method takes only one class at a time, so the JVM calls this method multiple times consecutively), which calls transform()sun.instrument.TransformerManager object 上,其中列出了所有已注册的ClassTransformers并调用每个转换器来转换类(我只注册了一个转换器!! )。

所以在我看来,大部分时间都花在了 JVM 上(在调用retransformClasses0()之后和每次调用sun.instrument.InstrumentationImpl.transform()之前)。 有没有办法减少 JVM 执行此任务所需的时间?

更正:

因为retransformClasses(classArr)不会立即重新转换classArr中的所有元素,而是会根据需要重新转换每个元素(例如,在链接时)。(请参阅 jdk [ VM_RedefineClasses ][1] 和 [ jvmtiEnv ][ 2]) , 它确实一次重新转换所有这些。

retransformClasses() 的作用:

  1. 将控制权转移到原生层,并给它一个我们想要转换的 class 列表
  2. 对于每个要转换的 class,本机代码会尝试通过调用我们的 java 转换器来获取新版本,这会导致 java 代码和本机代码之间的控制转移。
  3. 本机代码将内部表示的适当部分替换为给定的新 class 版本。

在第 1 步中:

java.lang.instrument.Instrumentation#retransformClasses调用sun.instrument.InstrumentationImpl#retransformClasses0是一个 JNI 方法,控制将转移到原生层。

// src/hotspot/share/prims/jvmtiEnv.cpp
jvmtiError
JvmtiEnv::RetransformClasses(jint class_count, const jclass* classes) {
  ...
  VM_RedefineClasses op(class_count, class_definitions, jvmti_class_load_kind_retransform);
  VMThread::execute(&op);
  ...
} /* end RetransformClasses */

在第 2 步中:

此步骤由KlassFactory::create_from_stream实现,该过程将发布一个ClassFileLoadHook事件,该事件的回调可以通过调用 java 转换器方法获取转换后的字节码。 在此步骤中,控件将在本机代码和 java 代码之间来回切换。

// src/hotspot/share/classfile/klassFactory.cpp
// check and post a ClassFileLoadHook event before loading a class
// Skip this processing for VM hidden or anonymous classes
if (!cl_info.is_hidden() && (cl_info.unsafe_anonymous_host() == NULL)) {
  stream = check_class_file_load_hook(stream,
                                      name,
                                      loader_data,
                                      cl_info.protection_domain(),
                                      &cached_class_file,
                                      CHECK_NULL);
}
//src/java.instrument/share/native/libinstrument/JPLISAgent.c :
//call java code sun.instrument.InstrumentationImpl#transform
transformedBufferObject = (*jnienv)->CallObjectMethod(
   jnienv,
   agent->mInstrumentationImpl, //sun.instrument.InstrumentationImpl
   agent->mTransform, //transform
   moduleObject,
   loaderObject,
   classNameStringObject,
   classBeingRedefined,
   protectionDomain,
   classFileBufferObject,
   is_retransformer);

在第 3 步中:

VM_RedefineClasses::redefine_single_class(jclass the_jclass, InstanceKlass* scratch_class, TRAPS)方法将目标 class 中的部分(例如常量池、方法等)替换为来自转换后的 class 的部分。

// src/hotspot/share/prims/jvmtiRedefineClasses.cpp
for (int i = 0; i < _class_count; i++) {
  redefine_single_class(_class_defs[i].klass, _scratch_classes[i], thread);
}

那么如何加快运行时 Java 代码检测呢?

在我的项目中,如果应用程序在转换时处于暂停的 state 中,则total时间和max-min时间几乎相同。 你能提供一些演示代码吗?

改变 jvm 的工作方式是不可能的,所以多线程可能不是一个坏主意。 在我的演示项目中使用多线程后,它的速度提高了好几倍。

从您的描述看来,完整的转换似乎是在单个线程中运行的。

您可以创建多个线程,每个线程都在转换一个 class。 由于 class 的转换应该独立于任何其他 class。 这应该可以通过执行系统上可用的已用核心数量的因素来缩短整体转换时间。

您可以使用以下方法计算核心:

int cores = Runtime.getRuntime().availableProcessors();

将要转换为核心数量的类列表分块,并创建可以并行处理块的线程。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM