I enabled JVMTI ClassLoad events which are supposed to be generated when a class is first loaded. I expected to get this event exactly once per class that was loaded but for some classes it seems to be generated multiple times, e.g. this one I get twice:
ClassLoad: Ljava/util/concurrent/ThreadFactory; loaded by thread 1
ClassLoad: Ljava/util/concurrent/ThreadFactory; loaded by thread 1
In systemDictionary.cpp
I found three occurances of JvmtiExport::post_class_load
which I assume is the code responsible to execute the callback (if any). They are in
SystemDictionary::resolve_instance_class_or_null
SystemDictionary::parse_stream
SystemDictionary::define_instance_class
But I do not understand the whole flow yet and hence it's not clear why I'm getting the event multiple times.
Is there anything I can do to prevent this or do I need to take care of that myself and adjust the code to ignore more than one event for the same class?
It's OK to see the event twice. This means the class resolution happens twice in the context of different class loaders. First, it is resolved by the Bootstrap class loader, then by the System class loader - sun.misc.Launcher.AppClassLoader
.
The utility from this answer will shed light on the circumstances when such events happen.
Consider a simple example:
public class Test {
public void setThreadFactory(ThreadFactory factory) {
}
public static void main(String[] args) {
}
}
When the launcher looks for public static void main()
, it creates Method
objects for public methods, and this causes resolution of all classes in the signatures of such methods.
The first ClassLoad event:
Class loaded: java/util/concurrent/ThreadFactory
- ClassLoad(_jvmtiEnv*, JNIEnv_*, _jobject*, _jclass*) + 0x69
- JvmtiExport::post_class_load(JavaThread*, Klass*) + 0x15b
- SystemDictionary::define_instance_class(instanceKlassHandle, Thread*) + 0x3cc
- SystemDictionary::find_or_define_instance_class(Symbol*, Handle, instanceKlassHandle, Thread*) + 0x35d
- SystemDictionary::load_instance_class(Symbol*, Handle, Thread*) + 0x20c
- SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, Thread*) + 0x78c
- JVM_FindClassFromBootLoader + 0x22b
- Java_java_lang_ClassLoader_findBootstrapClass + 0x9b
* java/lang/ClassLoader.findBootstrapClass @ -1
* java/lang/ClassLoader.findBootstrapClassOrNull @ 12
* java/lang/ClassLoader.loadClass @ 48
* java/lang/ClassLoader.loadClass @ 38
* sun/misc/Launcher$AppClassLoader.loadClass @ 81
* java/lang/ClassLoader.loadClass @ 3
* 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
The second one:
Class loaded: java/util/concurrent/ThreadFactory
- 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*) + 0x18e
- 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