Причина и отслеживание загрузки классов во время проверки, выполнения метода и JIT-компиляции

Я пытаюсь понять, какие события приводят к загрузке классов, очень детально, и во время тестирования я обнаружил одно поведение, которое я не понимаю в этом очень простом примере:

public class ClinitTest {
    public static Integer num;
    public static Long NUMTEST;

    static {
        NUMTEST = new Long(15);;
        num = (int) (NUMTEST * 5);
        System.out.println(num);
    }

    public static void main(String[] args) {
        System.out.println( "The number is " + num);
    }
}

При беге java.lang.Long загружается при выполнении <clinit>, Что ж, он загружается ранее загрузчиком классов, но AppClassloader вызывается в этот момент, так как он еще не зарегистрирован как инициирующий загрузчик классов. Таким образом, LauncherHelper получит класс приложения, и прежде чем он сможет вызвать метод main, JVM обеспечит инициализацию класса. Во время исполнения <clinit> происходит загрузка этого класса.

В другом случае я использую Java-агент для переименования <clinit> к чему-то другому и вместо этого добавьте пустой. Я ожидал, что - так как оригинал <clinit> код не выполняется, я бы не получил события загрузки класса.

Странно кажется, что в это время нагрузка java.lang.Long происходит гораздо раньше, хотя. В своем следе я вижу, что это срабатывает, когда LauncherHelper пытается проверить основной класс. Здесь он пытается получить метод main с помощью отражения и вызова java.lang.Class.getDeclaredMethods0() под капотом приводит к вызову AppClassLoader просить java.lang.Long,

Итак, вопросы:

  1. Как это возможно, что в обычное время выполнения класс загружается позже (то есть, когда код фактически выполняется), но загружается так рано, когда код фактически не должен выполняться, потому что переименованный клиник никогда не вызывается?

  2. Есть ли способ в JVM отследить, какие события приводят к таким нагрузкам класса? Не только когда это происходит, но на самом деле, какая инструкция или событие приводит к этому, поскольку это может быть вызвано первым использованием класса, проверением другого класса, компиляцией JIT и т. Д.

1 ответ

Решение

С помощью агента, который подписывается на событие JVMTI ClassLoad, я подтвердил, что java.lang.Long не загружается при запуске ClinitTest со статической инициализацией удалено.

Поскольку вы запускаете тест с агентом Java, я полагаю, что либо

  • java.lang.Long загружается самим агентом во время преобразования вашего класса;
  • или агент добавляет / изменяет открытый метод с Long класс в подписи.

когда LauncherHelper проверяет основной класс, перебирает открытые методы, ищет public static void main(), Как побочный эффект, все классы, упомянутые в сигнатурах этих методов, разрешены.

Я не знаю о существующем инструменте, который позволяет отслеживать загрузку классов относительно внутренних событий JVM, но такой инструмент может быть легко написан в нескольких строках кода. Вот.

#include <jvmti.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <libunwind.h>
#include <cxxabi.h>

static char* fix_class_name(char* class_name) {
    class_name[strlen(class_name) - 1] = 0;
    return class_name + 1;
}

static void print_native_backtrace() {
    unw_context_t context;
    unw_cursor_t cursor;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);

    char func[256];
    unw_word_t offs;
    while (unw_step(&cursor) > 0 && unw_get_proc_name(&cursor, func, sizeof(func), &offs) == 0) {
        if (func[0] == '_' && func[1] == 'Z') {
            int status;
            char* demangled = abi::__cxa_demangle(func, NULL, NULL, &status);
            if (demangled != NULL) {
                strncpy(func, demangled, sizeof(func));
                free(demangled);
            }
        }
        printf("  - %s + 0x%x\n", func, offs);
    }
}

static void print_java_backtrace(jvmtiEnv *jvmti) {
    jvmtiFrameInfo framebuf[256];
    int num_frames;
    if (jvmti->GetStackTrace(NULL, 0, 256, framebuf, &num_frames) == 0 && num_frames > 0) {
        for (int i = 0; i < num_frames; i++) {
            char* method_name = NULL;
            char* class_name = NULL;
            jclass method_class;

            jvmtiError err;
            if ((err = jvmti->GetMethodName(framebuf[i].method, &method_name, NULL, NULL)) == 0 &&
                (err = jvmti->GetMethodDeclaringClass(framebuf[i].method, &method_class)) == 0 &&
                (err = jvmti->GetClassSignature(method_class, &class_name, NULL)) == 0) {
                printf("  * %s.%s + %ld\n", fix_class_name(class_name), method_name, framebuf[i].location);
            } else {
                printf(" [jvmtiError %d]\n", err);
            }

            jvmti->Deallocate((unsigned char*)class_name);
            jvmti->Deallocate((unsigned char*)method_name);
        }
    }
}

void JNICALL ClassLoad(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jclass klass) {
    char* class_name;
    jvmti->GetClassSignature(klass, &class_name, NULL);
    printf("Class loaded: %s\n", fix_class_name(class_name));
    jvmti->Deallocate((unsigned char*)class_name);

    print_native_backtrace();
    print_java_backtrace(jvmti);
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
    jvmtiEnv *jvmti;
    vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0);

    jvmtiEventCallbacks callbacks = {0};
    callbacks.ClassLoad = ClassLoad;
    jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
    jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL);

    return 0;
}

Обобщение:

g++ -shared -fPIC -olibclassload.so classload.c -lunwind -lunwind-x86_64

Бежать:

java -agentpath:/path/to/libclassload.so ClinitTest

Он будет показывать смешанную трассировку стека (C + Java) всякий раз, когда происходит событие загрузки класса, например

Class loaded: java/lang/Long
  - ClassLoad(_jvmtiEnv*, JNIEnv_*, _jobject*, _jclass*) + 0x69
  - JvmtiExport::post_class_load(JavaThread*, Klass*) + 0x15b
  - SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, Thread*) + 0x87c
  - SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, Thread*) + 0x33
  - get_mirror_from_signature(methodHandle, SignatureStream*, Thread*) + 0xc6
  - Reflection::get_parameter_types(methodHandle, int, oopDesc**, Thread*) + 0x5df
  - Reflection::new_method(methodHandle, bool, bool, Thread*) + 0xfc
  - get_class_declared_methods_helper(JNIEnv_*, _jclass*, unsigned char, bool, Klass*, Thread*) + 0x479
  - JVM_GetClassDeclaredMethods + 0xcb
  * java/lang/Class.getDeclaredMethods0 @ -1
  * java/lang/Class.privateGetDeclaredMethods @ 37
  * java/lang/Class.privateGetMethodRecursive @ 2
  * java/lang/Class.getMethod0 @ 16
  * java/lang/Class.getMethod @ 13
  * sun/launcher/LauncherHelper.validateMainClass @ 12
  * sun/launcher/LauncherHelper.checkAndLoadMain @ 214
Другие вопросы по тегам