【发布时间】: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