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

我制作了一个Java代理,该代理在运行时期间附加到JVM,并检测所有已加载的项目类并插入一些日志记录语句。总共有11k个课程。我测量了transformClassFileTransformer方法花费的总时间为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会调用transform()类的sun.instrument.InstrumentationImpl方法(但是此方法一次只使用一个类,因此JVM连续多次调用此方法),这会在具有列表的transform()对象上调用sun.instrument.TransformerManager。注册所有ClassTransformers并调用这些转换器中的每一个来转换类(我只有一个转换器注册!)。
因此,我认为,大部分时间都花在了JVM中(在retransformClasses0()调用之后和每次sun.instrument.InstrumentationImpl.transform()调用之前)。有没有一种方法可以减少JVM执行此任务所需的时间?

参考方案

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

  • 将控件转移到本机层,并为其提供一个我们要转换
  • 的类列表

  • 对于每个要转换的类,本机代码都试图通过调用我们的java转换器来获取新版本,这导致Java代码和本机之间的控制权转移。
  • 本机代码用给定的新类版本相互替换内部表示的相应部分。
  • 在步骤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)方法用转换后的类中的部分替换目标类中的部分(例如,常量池,方法等)。

    // 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的工作方式是不可能的,因此多线程可能不是一个坏主意。在我的演示项目中使用多线程后,它的速度提高了数倍。

    Java:静态字段在内存中的哪个位置? - java

    如果我们将对象存储在对象的静态字段中,那么JVM如何为它分配内存?它是否存在于“隐式”(不确定我是否使用正确的单词)类对象中?静态字段与对象字段有何不同? 参考方案 静态字段是类变量,并且在该类的所有实例之间共享。实例变量(或我认为您引用它们的对象字段)属于类的各个实例,并且不共享。至于它们存储在内存中的位置将根据JVM的实现而定,因此没有理由需要两个不同的…

    Java-非泛型类扩展了泛型类 - java

    我想知道如何(如果可能)创建一个类,它是泛型类的特定类型。具体来说,我有一个实现所有必需方法的abstract class Stack<Type>,我想有一个class StackInteger,而StackInteger的实例也是Stack<Integer>的实例。我看到我可以做这样的事情:class StackInteger { …

    Java:正则表达式模式匹配器是否有大小限制? - java

    我的模式类似于OR:“word1 | word2 | word3”我大约有800个字。可能有问题吗? 参考方案 您仅受记忆和理智的限制。 :)

    Java:线程池如何将线程映射到可运行对象 - java

    试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

    JAVA:字节码和二进制有什么区别? - java

    java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…