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
因此,问题是:
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