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



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

instrumentation.retransformClasses(myClassesArray);

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

更新
当我的代理被连接时,

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

只调用一次

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


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()。一段时间后(!(,JVM 调用sun.instrument.InstrumentationImpl类的transform()方法(但此方法一次只接受一个类,因此 JVM 连续多次调用此方法(,它调用transform()在具有所有注册ClassTransformers的列表的sun.instrument.TransformerManager对象上调用,并调用这些转换器中的每一个来转换类(我只注册了一个转换器!!

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

更正

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

retransformClasses(( 的作用:

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

在步骤 1 中:

java.lang.instrument.Instrumentation#retransformClasses调用 JNI 方法sun.instrument.InstrumentationImpl#retransformClasses0,控件将被转移到本机层。

// 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)方法将目标类中的部件(如常量池、方法等(替换为转换后的类中的部件。

// 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代码检测呢?

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

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

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

您可以创建多个线程,每个线程一次转换一个类。因为类的转换应该独立于任何其他类。这应该会让您将整体转换时间缩短,其倍数是执行系统上可用的已用核心数量的倍数。

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

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

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

最新更新