Java 验证、方法执行和JIT编译期间类加载的原因和跟踪

Java 验证、方法执行和JIT编译期间类加载的原因和跟踪,java,jvm,classloader,jit,Java,Jvm,Classloader,Jit,我试图非常详细地了解哪些事件导致类加载,在测试过程中,我遇到了一个在这个非常基本的示例中我不了解的行为: public class ClinitTest { public static Integer num; public static Long NUMTEST; static { NUMTEST = new Long(15);; num = (int) (NUMTEST * 5); System.out.println

我试图非常详细地了解哪些事件导致类加载,在测试过程中,我遇到了一个在这个非常基本的示例中我不了解的行为:

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
时,会在执行
时加载。好的,它在引导类加载器之前被加载,但是AppClassloader在那时被调用,因为它还没有注册为初始化类加载器。因此LauncherHelper将获得应用程序类,在调用main方法之前,JVM将确保该类已初始化。在执行
期间,会发生此类加载

在另一次运行中,我使用Java代理将
重命名为其他名称,并添加一个空的。我的期望是-由于原始的
代码没有得到执行,我也不会得到类加载事件

奇怪的是,此时,
java.lang.Long
的加载似乎发生得更早。在我的跟踪中,我看到当
LaunchHelper
尝试验证主类时,它被触发。在这里,它试图通过反射获取main方法,在引擎盖下调用
java.lang.Class.getDeclaredMethods0()
会导致调用
AppClassLoader
请求
java.lang.Long

因此,问题是:

  • 在正常执行时,类如何可能在稍后加载(即,当代码实际执行时),但它却在如此早的时间加载,而代码实际上永远不会执行,因为从未调用它

  • JVM中是否有方法跟踪导致此类类加载的事件?不仅是在什么时候发生,而且是哪个指令或事件导致了它,因为它可能是由第一次使用的类、验证的另一个类、JIT编译等引起的

  • 在订阅JVMTI事件的用户的帮助下,我已经验证了在删除静态初始化的情况下运行
    clinitest
    时未加载
    java.lang.Long

    由于您正在使用Java代理运行测试,我认为

    • java.lang.Long
      在类转换期间由代理自身加载
    • 或者代理在签名中添加/修改带有
      Long
      类的公共方法
    launchelper
    验证主类时,它会遍历公共方法,查找
    publicstaticvoidmain()
    。作为一个副作用,这些方法的签名中提到的所有类都被解析

    我不知道现有的工具允许跟踪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;
    }
    
    运行:

    每当类加载事件发生时,它将显示混合堆栈跟踪(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
    

    令人惊叹的您的小跟踪工具有助于证明您已经假设的:根本没有Java代理,但它确实是一个新生成的公共setter方法,我的一位同事为了测试目的在众多转换器中添加了该方法。我没有意识到这一点,他忘了提。由于没有C跟踪,我只看到
    Class.getDeclaredMethods0
    后面跟着一个类加载,我感到困惑,因为当我更改静态变量的类型时,加载的类型也发生了变化(现在知道有一个生成的getter方法并看到本机跟踪是有意义的)。谢谢
    java -agentpath:/path/to/libclassload.so ClinitTest
    
    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