我试图非常详细地了解哪些事件导致了类加载,并且在测试过程中遇到了一种非常简单的示例中我不理解的行为:
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}}的调用。
所以问题是:
怎么可能在正常执行时稍后加载该类(即,当代码实际执行时),却又这么早加载,以至于该代码实际上永远不会执行,因为从未调用重命名的clinit ?
JVM中是否有一种方法可以跟踪导致此类加载的事件?不仅发生在什么时候,而且导致它的实际上是哪个指令或事件,因为它可能是由第一次使用一个类,正在验证另一个类,编译了JIT等导致的。
答案 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