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

时间:2018-08-15 18:07:23

标签: 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(num);
    }

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

运行java.lang.Long时会加载<clinit>。好吧,它是由引导类加载器较早加载的,但此时AppClassloader被调用,因为它尚未注册为启动类加载器。因此,LauncherHelper将获取应用程序类,并且在调用主方法之前,JVM将确保该类已初始化。在执行<clinit>期间,将发生此类加载。

在另一次运行中,我使用Java代理将<clinit>重命名为其他名称,然后添加一个空的名称。我的期望是-由于原始<clinit>代码未执行,因此我也不会获得类加载事件。

奇怪的是,此时java.lang.Long的加载发生在更早的时间。在我的跟踪中,我看到LauncherHelper试图验证主类时触发了它。在这里,它试图通过反射来获取main方法,并且在幕后对java.lang.Class.getDeclaredMethods0()的调用导致调用AppClassLoader的{​​{1}}的调用。

所以问题是:

  1. 怎么可能在正常执行时稍后加载该类(即,当代码实际执行时),却又这么早加载,以至于该代码实际上永远不会执行,因为从未调用重命名的clinit ?

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

1 个答案:

答案 0 :(得分:3)

借助agent订阅了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