【问题标题】:JVMTI RetransformClasses() is taking a lot of timeJVMTI RetransformClasses() 花费了很多时间
【发布时间】:2017-05-10 17:09:17
【问题描述】:

我部署了一个简单的 JVMTI 代理来测试字节码检测。我的策略是在CompiledMethodLoad回调中调用RetransformClasses函数来调用ClassFileLoadHook。为此,我编写了以下代码:

    err = (*jvmti)->GetMethodDeclaringClass(jvmti, method, &klass);
    check_jvmti_error(jvmti, err, "Get Declaring Class");

    err = (*jvmti)->RetransformClasses(jvmti, 1, &klass);
    check_jvmti_error(jvmti, err, "Retransform class");

这个函数通过调用ClassFileLoadHook 事件可以正常工作,但是当我只是在其中传递同一个类时它需要很多时间。我的ClassFileLoadHook 回调函数为空。我正在计算一个简单的矩阵乘法算法的时间。通过注释掉RetransformClasses 函数,我得到了0.8 seconds 顺序的执行时间。而仅编写此函数会将执行时间提升到 15 seconds 左右。

它应该花费那么多开销还是我做错了什么?

问候

代码:

static int x = 1;
void JNICALL
compiled_method_load(jvmtiEnv *jvmti, jmethodID method, jint code_size,
        const void* code_addr, jint map_length, const jvmtiAddrLocationMap* map,
        const void* compile_info) {
    jvmtiError err;
    jclass klass;

    char* name = NULL;
    char* signature = NULL;
    char* generic_ptr = NULL;

    err = (*jvmti)->RawMonitorEnter(jvmti, lock);
    check_jvmti_error(jvmti, err, "raw monitor enter");

    err = (*jvmti)->GetMethodName(jvmti, method, &name, &signature,
            &generic_ptr);
    check_jvmti_error(jvmti, err, "Get Method Name");

    printf("\nCompiled method load event\n");
    printf("Method name %s %s %s\n\n", name, signature,
            generic_ptr == NULL ? "" : generic_ptr);

    if (strstr(name, "main") != NULL && x == 1) {
        x++;
        err = (*jvmti)->GetMethodDeclaringClass(jvmti, method, &klass);
        check_jvmti_error(jvmti, err, "Get Declaring Class");

        err = (*jvmti)->RetransformClasses(jvmti, 1, &klass);
        check_jvmti_error(jvmti, err, "Retransform class");

    }

    if (name != NULL) {
        err = (*jvmti)->Deallocate(jvmti, (unsigned char*) name);
        check_jvmti_error(jvmti, err, "deallocate name");
    }
    if (signature != NULL) {
        err = (*jvmti)->Deallocate(jvmti, (unsigned char*) signature);
        check_jvmti_error(jvmti, err, "deallocate signature");
    }
    if (generic_ptr != NULL) {
        err = (*jvmti)->Deallocate(jvmti, (unsigned char*) generic_ptr);
        check_jvmti_error(jvmti, err, "deallocate generic_ptr");
    }

    err = (*jvmti)->RawMonitorExit(jvmti, lock);
    check_jvmti_error(jvmti, err, "raw monitor exit");
}

【问题讨论】:

  • 嗯,重新改造并不便宜。您是否为所有编译方法执行上述代码? CompiledMethodLoad 调用对执行时间影响很大的热方法,通过重新转换它们的类,您 1)支付重新创建类的成本 2)删除已编译的方法,有效地迫使 VM 在解释器中执行热方法
  • 我只是重新转换一个类文件,它只包含一种方法。它不应该那么昂贵。 0.8 秒和15 秒之间没有可比性。好像不是什么成本。此外,即使程序开始在解释器中执行,由于TieredCompilation 模式,它应该再次恢复到编译模式。如果我错了纠正我。 \\ 实际上,我只想检测“热”方法。有什么方法可以在重新转换后使用 JIT?
  • 我预计会出现部分死锁情况。 JVMTI Agent Deadlock
  • 1) 很难说只看到那一小部分代码。你能显示整个CompiledMethodLoad 回调吗? 2)你确定你只改造一个班级吗?您是否特别确保您正在重新转换您想要的课程?例如,您需要在调用 retransform 之前检查声明的类名。
  • 3) TieredCompilation 只表示有多个编译级别。在你重新转换了一个类之后,VM 必须丢弃所有为它编译的代码。如果每次 VM 编译它的方法时重新转换一个类,它总是会丢弃已编译的代码。您可以在重新转换后使用 JIT,只是不应该在每次 JIT 为您完成某些操作时重新转换同一个类。 4)死锁是指应用程序由于相互锁定而无法继续运行,而不是运行缓慢。但是,重转换必须做一些同步,这也可能是慢的根源。

标签: java performance jvm-hotspot bytecode-manipulation jvmti


【解决方案1】:

回答我的问题:

没有。我没有做错什么。应该会占用这么多开销。

证明如下:

我使用Jitwatch 来了解问题。我在 JIT 调用后分析了 ClassLoad 时间检测和检测。我在这两种情况下都使用相同的应用程序代码。

类加载时间检测

执行时间:大约 18 秒

JIT 调用期间的仪表

执行时间:大约 80 秒

结论

我们可以在这里清楚地看到,当我尝试通过在CompiledLoadEvent 中调用RetransformClasses -> CLassFileLoadHook 序列来检测我的代码时,JIT 只是暂停,然后永远不会为我尝试检测的函数调用。之后它甚至不进行 OSR 编译。我在answer 中总结了 JIT 这种行为的原因。后续问题是here。任何知道解决方法的人都非常欢迎回答。

【讨论】:

  • 我尝试重新转换在我附加到 JVM 之前加载的每个类,我的 CPU 使用率大幅下降,就像 JVM 在重新转换发生时所做的工作一样少。你有没有发现类似的东西?
猜你喜欢
  • 2015-08-02
  • 2011-06-21
  • 1970-01-01
  • 1970-01-01
  • 2016-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多