验证,方法执行和JIT编译过程中类加载的原因和跟踪 [英] Reason and tracing of class loading during verification, method execution and JIT compilation

查看:164
本文介绍了验证,方法执行和JIT编译过程中类加载的原因和跟踪的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试了解哪些事件会在非常详细的基础上导致类加载,并且在我的测试期间遇到了一个我在这个非常基本的示例中无法理解的行为:

I'm trying to understand which events lead to class loads on a very detailed basis and during my testing encountered one behaviour I do not understand in this very basic sample:

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> 时被加载。好吧,它先前由bootstrap类加载器加载,但此时调用了AppClassloader,因为它尚未注册为启动类加载器。因此,LauncherHelper将获取应用程序类,在它调用main方法之前,JVM将确保该类已初始化。在执行< clinit> 期间,此类加载发生。

When running java.lang.Long gets loaded while executing the <clinit>. Well, it gets loaded earlier by bootstrap classloader but the AppClassloader is called at that point as it is not yet registered as initiating classloader. So the LauncherHelper will get the application class and before it can invoke the main method the JVM will ensure the class is initialized. During execution of <clinit> this class load happens.

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

In another run I use a Java agent to rename the <clinit> to something else and add an empty one instead. My expectation was that - since the original <clinit> code does not get executed I would not get the class load events either.

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

Strangely it seems that at this time the load of java.lang.Long happens at a much earlier time though. In my trace I see that it gets triggered when the LauncherHelper tries to validate the main class. Here it tries to get the main method via reflection and the call to java.lang.Class.getDeclaredMethods0() under the hood leads to the invocation of the AppClassLoader asking for java.lang.Long.

所以问题是:


  1. 怎么可能在正常执行时,该类稍后被加载(即当代码实际执行时),但是当代码实际上永远不会被执行时它被加载得如此早,因为重命名的clinit从未被调用过?

  1. How is it possible that at normal execution time the class is loaded later (i.e. when the code gets actually executed) but it is loaded so early when the code should actually never get executed because the renamed clinit is never called?

JVM中是否有办法跟踪哪些事件会导致此类加载?不仅在它何时发生,而且实际上是哪个指令或事件导致它,因为它可能是由首次使用的类,另一个正在验证的类,JIT编译等引起的。

Is there a way in the JVM to trace which events lead to such class loads? Not just when it happens, but really which instruction or event lead to it as it could be caused by a class being first used, another class being verified, JIT compiled, etc.


推荐答案

借助代理商 ClassLoad 事件我已经验证 java.lang.Long 在运行 ClinitTest时已加载已删除静态初始化。

With the help of an agent that subscribes to JVMTI ClassLoad event I've verified that java.lang.Long is not loaded when running ClinitTest with static initialized removed.

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

Since you are running a test with a Java agent, I suppose that either


  • java.lang.Long 在您的课程转换过程中由代理自己加载;

  • 或代理在签名中使用 Long 类添加/修改公共方法。

  • java.lang.Long is loaded by the agent itself during the transformation of your class;
  • or the agent adds/modifies a public method with Long class in the signature.

<$ c时$ c> LauncherHelper 验证主类,它遍历寻找 public static void main()的公共方法。作为副作用,这些方法的签名中提到的所有类都被解析。

When LauncherHelper validates the main class, it traverses public methods looking for public static void main(). As a side effect, all classes mentioned in the signatures of these methods are resolved.

我不知道现有的工具允许跟踪类加载JVM内部事件,但这样的工具可以很容易地用几行代码编写。在这里。

I'm not aware of an existing tool that allows to trace class loading with respect to JVM internal events, but such a tool can be easily written in a few lines of code. Here it is.

#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),例如

It will show a mixed stack trace (C + Java) whenever a class load event happens, e.g.

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

这篇关于验证,方法执行和JIT编译过程中类加载的原因和跟踪的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆