[英]How to speed up runtime Java code instrumentation?
I made a Java agent which is attached to a JVM during runtime and instruments all the loaded project classes and inserts some logging statements.我制作了一个 Java 代理,它在运行时附加到 JVM 并检测所有加载的项目类并插入一些日志记录语句。 There are 11k classes in total.
总共有 11k 个班级。 I measured the total time taken by the
transform
method of my ClassFileTransformer
and it was 3 seconds.我测量了我的
ClassFileTransformer
的transform
方法所花费的总时间,它是 3 秒。 But the duration of the whole instrumentation process takes about 30 seconds.但是整个检测过程的持续时间大约需要 30 秒。 This is how I retransform my classes:
这就是我重新转换课程的方式:
instrumentation.retransformClasses(myClassesArray);
I assume most time is taken up by the JVM to reload changed classes.我假设 JVM 占用了大部分时间来重新加载更改的类。 Is that right?
那正确吗? How can I speed up the instrumentation process?
如何加快检测过程?
Update :更新:
When my agent is attached,当我的代理人被附加时,
instrumentation.addTransformer(new MyTransfomer(), true);
instrumentation.retransformClasses(retransformClassArray);
is called only once .只调用一次。
Then MyTransfomer
class instruments the classes and measures the total duration time of instrumentation:然后
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;
}
}
After all the classes are instrumented(from the initial array) (a global cache keeps track of the instrumented classes) total
is printed and it will be ~3 seconds.在检测所有类之后(从初始数组)(全局缓存跟踪检测的类)
total
打印出来,大约是 3 秒。 But max-min
is ~30 seconds.但
max-min
约为 30 秒。
Update 2:更新 2:
After looking at the stack trace this is what happens: I call查看堆栈跟踪后会发生以下情况:我调用
instrumentation.retransformClasses(retransformClassArray);
which calls the native method retransformClasses0()
.它调用本机方法
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()
on the sun.instrument.TransformerManager
object which has a list with the all the ClassTransformers
registered and calls each of these transformers to transform the class( I have only one transformer registered!! ). 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
并调用每个转换器来转换类(我只注册了一个转换器!! )。
So to my opinion, most of the time is spent in the JVM (after retransformClasses0()
is called and before each call to sun.instrument.InstrumentationImpl.transform()
).所以在我看来,大部分时间都花在了 JVM 上(在调用
retransformClasses0()
之后和每次调用sun.instrument.InstrumentationImpl.transform()
之前)。 Is there a way to reduce the time needed by the JVM to carry out this task?有没有办法减少 JVM 执行此任务所需的时间?
Correction:更正:
Because the , it does retransform all of them at once. retransformClasses(classArr)
will not retransform all the elements in the classArr
at once, instead it will retransform each of them as needed(eg. while linking).(refer to the jdk [ VM_RedefineClasses
][1] and [ jvmtiEnv
][2])因为
retransformClasses(classArr)
不会立即重新转换classArr
中的所有元素,而是会根据需要重新转换每个元素(例如,在链接时)。(请参阅 jdk [ VM_RedefineClasses
][1] 和 [ jvmtiEnv
][ 2]), 它确实一次重新转换所有这些。
What retransformClasses() does: retransformClasses() 的作用:
In step 1:在第 1 步中:
java.lang.instrument.Instrumentation#retransformClasses
calls sun.instrument.InstrumentationImpl#retransformClasses0
which is a JNI method, the control will be transferred to native layer. 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 */
In step 2:在第 2 步中:
This step is implemented by KlassFactory::create_from_stream
, this procedure will post a ClassFileLoadHook
event whose callback can acquire the transformed bytecode by invoking the java transformer method.此步骤由
KlassFactory::create_from_stream
实现,该过程将发布一个ClassFileLoadHook
事件,该事件的回调可以通过调用 java 转换器方法获取转换后的字节码。 In this step, the control will switch back and forth between native code and java code.在此步骤中,控件将在本机代码和 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);
In step 3:在第 3 步中:
VM_RedefineClasses::redefine_single_class(jclass the_jclass, InstanceKlass* scratch_class, TRAPS)
method replaces parts (such as constant pool, methods, etc.) in target class with parts from transformed class. 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);
}
So how to speed up runtime Java code instrumentation?那么如何加快运行时 Java 代码检测呢?
In my project, the total
time and max-min
time are almost the same if the app is in a paused state while transforming.在我的项目中,如果应用程序在转换时处于暂停的 state 中,则
total
时间和max-min
时间几乎相同。 can you provide some demo code?你能提供一些演示代码吗?
It's impossible to change the way jvm works, so multithreading may not be a bad idea.改变 jvm 的工作方式是不可能的,所以多线程可能不是一个坏主意。 It got several times faster after using multithreading in my demo project.
在我的演示项目中使用多线程后,它的速度提高了好几倍。
From your description it seems like the complete transformation is running in a single thread.从您的描述看来,完整的转换似乎是在单个线程中运行的。
You could create multiple threads, each one is transforming one class at the time.您可以创建多个线程,每个线程都在转换一个 class。 As the transformation of a class should be independent of any other class.
由于 class 的转换应该独立于任何其他 class。 This should give you an improvement in the overall transformation time by a factor of the number of used Core available on the executing system.
这应该可以通过执行系统上可用的已用核心数量的因素来缩短整体转换时间。
You can count the cores with:您可以使用以下方法计算核心:
int cores = Runtime.getRuntime().availableProcessors();
Chunk the list of classes to be transformed into the number of cores and create that may threads to process the chunks in parallel.将要转换为核心数量的类列表分块,并创建可以并行处理块的线程。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.