验证、方法执行、JIT编译时加载class的原因及追踪

Reason and tracing of class loading during verification, method execution and JIT compilation

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

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);
    }
}

当 运行ning java.lang.Long 在执行 <clinit> 时被加载。好吧,它是由 bootstrap classloader 更早加载的,但 AppClassloader 在此时被调用,因为它尚未注册为启动 classloader。因此,LauncherHelper 将获取应用程序 class,并且在它可以调用 main 方法之前,JVM 将确保 class 已初始化。在执行 <clinit> 期间,会发生此 class 负载。

在另一个 运行 中,我使用 Java 代理将 <clinit> 重命名为其他名称并添加一个空名称。我的期望是——因为原始的 <clinit> 代码没有被执行,所以我也不会得到 class 加载事件。

奇怪的是,此时 java.lang.Long 的负载似乎发生在更早的时间。在我的跟踪中,我看到它在 LauncherHelper 尝试验证主 class 时被触发。这里它试图通过反射获取主要方法,并且在后台调用 java.lang.Class.getDeclaredMethods0() 导致调用 AppClassLoader 请求 java.lang.Long

所以问题是:

  1. 怎么可能在正常执行时 class 加载得晚(即当代码实际执行时),但加载得这么早,而代码实际上永远不会加载因为重命名的 clinit 从未被调用而被执行?

  2. JVM 中是否有一种方法可以跟踪哪些事件导致此类 class 负载?不仅仅是它发生的时间,而是真正导致它的指令或事件,因为它可能是由 class 被首次使用、另一个 class 被验证、JIT 编译等引起的。

借助 agent that subscribes to JVMTI ClassLoad 事件,我已验证 java.lang.Long 在 运行 ClinitTest 加载删除了静态初始化。

因为你是 运行 一个 Java 代理人的测试,我想

  • java.lang.Long 是在你的 class;
  • 转换过程中由代理自己加载的
  • 或代理 adds/modifies 签名中带有 Long class 的 public 方法。

LauncherHelper 验证主要 class 时,它遍历 public 方法寻找 public static void main()。作为副作用,这些方法的签名中提到的所有 classes 都已解析。

我不知道是否有允许跟踪 class 加载 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

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