JVMTI RetransformClasses() занимает много времени

Я развернул простой агент JVMTI для тестирования инструментария байт-кода. Моя стратегия состоит в том, чтобы вызвать функцию RetransformClasses в обратном вызове CompiledMethodLoad, чтобы вызвать 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");
}

person Saqib Ahmed    schedule 26.12.2016    source источник
comment
Ну, ретрансляция не из дешевых. Выполняете ли вы приведенный выше код для всех скомпилированных методов? CompiledMethodLoad вызывается для горячих методов, которые сильно влияют на время выполнения, и, повторно преобразовывая их классы, вы 1) оплачиваете стоимость воссоздания класса 2) удаляете скомпилированные методы, фактически заставляя VM выполнять горячие методы в интерпретаторе.   -  person Stanislav Lukyanov    schedule 26.12.2016
comment
Я просто повторно преобразовываю один файл класса, и он содержит только один метод. Это не должно быть так дорого. Нет никакого сравнения между 0.8 секундой и 15 секундой. Это не похоже на затраты. Более того, даже если программа начнет выполняться в интерпретаторе, из-за режима TieredCompilation она должна снова вернуться в скомпилированный режим. Поправьте меня если я ошибаюсь. \\ На самом деле я хочу инструментировать только горячие методы. Можно ли как-нибудь использовать JIT даже после повторного преобразования?   -  person Saqib Ahmed    schedule 27.12.2016
comment
Я ожидаю своего рода ситуацию с частичной тупиковой блокировкой. Взаимоблокировка агента JVMTI   -  person Saqib Ahmed    schedule 27.12.2016
comment
1) Трудно сказать больше, видя только эту маленькую часть кода. Можете ли вы показать весь обратный вызов CompiledMethodLoad? 2) Вы уверены, что переделываете только один класс? Вы специально гарантируете, что повторно преобразовываете класс, который хотите? Вам нужно будет, например, проверить имя объявляющего класса перед вызовом retransform.   -  person Stanislav Lukyanov    schedule 27.12.2016
comment
3) TieredCompilation означает только наличие нескольких уровней компиляции. После того, как вы повторно преобразовали класс, VM должна выбросить весь скомпилированный для него код. Если вы повторно преобразовываете класс каждый раз, когда VM компилирует свой метод, он всегда отбрасывает скомпилированный код. Вы можете использовать JIT после повторного преобразования, вы просто не должны повторно преобразовывать один и тот же класс каждый раз, когда JIT что-то делает для вас. 4) Взаимная блокировка — это когда приложение не может продолжать работу из-за взаимной блокировки, а не когда оно работает медленно. Однако повторное преобразование должно выполнять некоторую синхронизацию, что также может быть причиной замедления.   -  person Stanislav Lukyanov    schedule 27.12.2016
comment
Я просто повторно преобразовываю скомпилированные методы один раз. Во время их первого запуска. После этого их можно свободно казнить. Во всяком случае, я понял новую проблему и задал ее в отдельном вопросе. Посмотри. Буду благодарен за любую помощь. Инструментарий динамического байт-кода завершается с ошибкой   -  person Saqib Ahmed    schedule 28.12.2016
comment
Я добавил в вопрос всю функцию обратного вызова CompiledLoadMethod. Код внутри блока if выполняется только один раз. Сообщите мне, если потребуется какая-либо другая информация.   -  person Saqib Ahmed    schedule 28.12.2016
comment
Я уверен, что трансформирую только один класс. Я уверен, что преобразовываю класс, который хочу.   -  person Saqib Ahmed    schedule 28.12.2016


Ответы (1)


Чтобы ответить на мой вопрос:

Нет. Я не делал ничего плохого. Это должно занять столько накладных расходов.

И вот доказательство:

Я использовал Jitwatch, чтобы получить некоторое представление о проблеме. Я профилировал как ClassLoad инструментирование времени, так и инструментирование после вызова JIT. Я использую один и тот же код приложения в обоих случаях.

Инструментарий времени загрузки класса

Вызов JIT во время загрузки класса

Время выполнения: примерно 18 секунд.

Инструментарий во время вызова JIT

Вызов JIT во время инструментирования после вызова JIT

Время выполнения: примерно 80 секунд.

Заключение

Мы можем ясно видеть здесь, что когда я пытаюсь инструментировать свой код, вызывая последовательность RetransformClasses -> CLassFileLoadHook в CompiledLoadEvent, JIT просто останавливается, а затем никогда не вызывается для функции, которую я пытался инструментировать. После этого он даже не компилирует OSR. Я резюмировал причину такого поведения JIT в этом ответ. Последующий вопрос задается /41997216?noredirect=1">здесь. Любой, кто знает обходной путь, может ответить.

person Saqib Ahmed    schedule 03.02.2017
comment
Я попытался повторно преобразовать каждый класс, который был загружен до того, как я подключился к JVM, и использование моего ЦП сильно упало, например, JVM выполняет меньше работы, когда происходят повторные преобразования. Вы заметили что-то подобное? - person Sam Thomas; 16.05.2019