I deployed a simple JVMTI agent to test bytecode instrumentation. My strategy is to call RetransformClasses
function in CompiledMethodLoad
call back to invoke ClassFileLoadHook
. I wrote following code to do so:
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");
This function works correctly by invoking ClassFileLoadHook
event, but it takes a lot of time while I'm just passing the same class inside it. My ClassFileLoadHook
callback function is empty. I'm counting time of a simple matrix multiplication algorithm. By commenting out RetransformClasses
function I get the execution time of the order of 0.8 seconds
. Whereas just writing this function elevates the execution time to around 15 seconds
.
Is it supposed to take that much overhead or am I doing something wrong?
Regards
Code:
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");
}
To answer my question:
And here's the proof:
I used Jitwatch to get some insight in the problem. I profiled both
ClassLoad
time instrumentation and instrumentation after JIT invocation. I'm using same application code in both cases.Class Load Time Instrumentation
Execution Time: 18 seconds approx.
Instrumentation During JIT invocation
Execution Time: 80 seconds approx.
Conclusion
We can clearly see here that when I try to instrument my code by calling
RetransformClasses
->CLassFileLoadHook
sequence inCompiledLoadEvent
, JIT simply halts and then never gets invoked for the function I tried to instrument. It doesn't even do OSR compilations afterward. I summed up the reason for this behaviour of JIT in this answer. The follow up question is given here. Anybody who knows a workaround is most welcome to answer.