簡體   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