具体描述过程需要参考jvm规范,本文只是追了一下代码
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-5.html#jvms-5.3
类加载,jvm启动后,对必须要的类进行了初始化,main方法运行加载时需要LauncherHelper
这里主要看了一下系统类加载器加载类过程,class文件读取成vm对象后进行了java class对象的转换,后续java才能使用类的class信息进行初始化
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
//find的实现
static FindClassFromBootLoader_t *findBootClass = NULL;
jclass
FindBootStrapClass(JNIEnv *env, const char* classname)
{
if (findBootClass == NULL) {
findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,
"JVM_FindClassFromBootLoader");
if (findBootClass == NULL) {
JLI_ReportErrorMessage(DLL_ERROR4,
"JVM_FindClassFromBootLoader");
return NULL;
}
}
return findBootClass(env, classname);
}
//连接到jvm.cpp
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
const char* name))
// Java libraries should ensure that name is never null or illegal.
if (name == nullptr || (int)strlen(name) > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
return nullptr;
}
assert(UTF8::is_legal_utf8((const unsigned char*)name, (int)strlen(name), false), "illegal UTF name");
TempNewSymbol h_name = SymbolTable::new_symbol(name);
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
if (k == nullptr) {
return nullptr;
}
if (log_is_enabled(Debug, class, resolve)) {
trace_class_resolution(k);
}
return (jclass) JNIHandles::make_local(THREAD, k->java_mirror());
JVM_END
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
获取到kclass,方法调用到systemDictionary.cpp
if (loaded_class == nullptr) {
// Do actual loading
loaded_class = load_instance_class(name, class_loader, THREAD);
}
InstanceKlass* SystemDictionary::load_instance_class(Symbol* name,
Handle class_loader,
TRAPS) {
InstanceKlass* loaded_class = load_instance_class_impl(name, class_loader, CHECK_NULL);
//...
return loaded_class;
}
load_instance_class_impl中判断是否有classloader,没有的时候用的一个nullptr名字创建一个新的,
有的就是java的classloader,这里应该就是系统的loader
if (k == nullptr) {
// Use VM class loader
//没用cds直接到了ClassLoader.cpp方法里面 ,ClassLoader.c是java对应类的jni,cpp是vm系统启动类
PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());
k = ClassLoader::load_class(class_name, search_only_bootloader_append, CHECK_NULL);
}
classLoader.cpp中
InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TRAPS) {
//...
//上一步搜索是从cds,本次从jimage(就是jar的升级),这里的_jrt_entry在vm初始化 Arguments::init_system_properties()进行的赋值
//jdk9以后的lib包下文件存储方式已经发生变化了,比如rt.jar不见了,jar被Modules优化替代了,用jimage方式加载jar
// Load Attempt #2: [jimage | exploded build]
if (!search_append_only && (nullptr == stream)) {
//java runtime 判断是不是实时的jar包加载方式,是的话从实时包里面搜
if (has_jrt_entry()) {
e = _jrt_entry;
stream = _jrt_entry->open_stream(THREAD, file_name);
} else {
// Exploded build - attempt to locate class in its defining module's location.
assert(_exploded_entries != nullptr, "No exploded build entries present");
stream = search_module_entries(THREAD, _exploded_entries, class_name, file_name);
}
}
// Load Attempt #3: [-Xbootclasspath/a]; [jvmti appended entries]
//从classpath的lib找对应的class文件
/**
这里的Xbootclasspath是系统获取额外的jar地址,通过不同的方式比如jvmti事件监听通过sun.instrument.InstrumentationImpl将一个jar包添加进启动包
ClassPathZipEntry* zip_entry = ClassLoader::create_class_path_zip_entry(segment, true);
里面调用到系统的路径方法get_canonical_path获取到系统的jar在文件系统的位置
*/
if (search_append_only && (nullptr == stream)) {
// For the boot loader append path search, the starting classpath_index
// for the appended piece is always 1 to account for either the
// _jrt_entry or the _exploded_entries.
assert(classpath_index == 0, "The classpath_index has been incremented incorrectly");
classpath_index = 1;
e = first_append_entry();
while (e != nullptr) {
stream = e->open_stream(THREAD, file_name);
if (nullptr != stream) {
break;
}
e = e->next();
++classpath_index;
}
}
//...
//将class从包里面搜索出来成为stream文件转换成kclass对象
InstanceKlass* result = KlassFactory::create_from_stream(stream,
name,
loader_data,
cl_info,
CHECK_NULL);
}
上面就是一个搜索解析class名称拿到文件流的过程,主要解析还在classFileParser.cpp里面
//构造方法在通过parse_stream方法解析常量池等,父类也会被关联初始化,主要就是初始化静态的常量表
ClassFileParser parser(stream,
name,
loader_data,
&cl_info,
ClassFileParser::BROADCAST, // publicity level
CHECK_NULL);
//隐藏类,后面jdk引入的
const ClassInstanceInfo* cl_inst_info = cl_info.class_hidden_info_ptr();
InstanceKlass* result = parser.create_instance_klass(old_stream != stream, *cl_inst_info, CHECK_NULL);
解析出来的InstanceKlass是虚拟机层面的,通过mirror映射出来java的class对象
InstanceKlass* ClassFileParser::create_instance_klass(bool changed_by_loadhook,
const ClassInstanceInfo& cl_inst_info,
TRAPS) {
if (_klass != nullptr) {
return _klass;
}
//根据解析出来的字段分配对应的metaspace大小
InstanceKlass* const ik =
InstanceKlass::allocate_instance_klass(*this, CHECK_NULL);
if (is_hidden()) {
mangle_hidden_class_name(ik);
}
//元空间分配,创建java mirror
fill_instance_klass(ik, changed_by_loadhook, cl_inst_info, CHECK_NULL);
assert(_klass == ik, "invariant");
return ik;
}
初始化class对象和静态字段信息
void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protection_domain, Handle classData,
Handle& mirror, Handle& comp_mirror, TRAPS) {
// Allocate mirror (java.lang.Class instance)
//Universe::heap中为java class分配空间地址
oop mirror_oop = InstanceMirrorKlass::cast(vmClasses::Class_klass())->allocate_instance(k, CHECK);
//当前也就是主线程的HandleArea保存class句柄,方便资源管理
mirror = Handle(THREAD, mirror_oop);
// Setup indirection from mirror->klass
set_klass(mirror(), k);
InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
assert(oop_size(mirror()) == mk->instance_size(k), "should have been set");
set_static_oop_field_count(mirror(), mk->compute_static_oop_field_count(mirror()));
//...
//初始化static字段,所以static代码块要比静态字段晚一点执行
initialize_mirror_fields(k, mirror, protection_domain, classData, THREAD);
//...
}