Причина и отслеживание загрузки классов во время проверки, выполнения метода и 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 получит класс приложения, и прежде чем он сможет вызвать метод main, JVM обеспечит инициализацию класса. Во время исполнения <clinit>
происходит загрузка этого класса.
В другом случае я использую Java-агент для переименования <clinit>
к чему-то другому и вместо этого добавьте пустой. Я ожидал, что - так как оригинал <clinit>
код не выполняется, я бы не получил события загрузки класса.
Странно кажется, что в это время нагрузка java.lang.Long
происходит гораздо раньше, хотя. В своем следе я вижу, что это срабатывает, когда LauncherHelper
пытается проверить основной класс. Здесь он пытается получить метод main с помощью отражения и вызова java.lang.Class.getDeclaredMethods0()
под капотом приводит к вызову AppClassLoader
просить java.lang.Long
,
Итак, вопросы:
Как это возможно, что в обычное время выполнения класс загружается позже (то есть, когда код фактически выполняется), но загружается так рано, когда код фактически не должен выполняться, потому что переименованный клиник никогда не вызывается?
Есть ли способ в JVM отследить, какие события приводят к таким нагрузкам класса? Не только когда это происходит, но на самом деле, какая инструкция или событие приводит к этому, поскольку это может быть вызвано первым использованием класса, проверением другого класса, компиляцией JIT и т. Д.
1 ответ
С помощью агента, который подписывается на событие 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