前一篇介绍了3种类加载器,每种类加载器都加载指定路径下的类库,它们在具体使用时并不是相互独立的,而是相互配合对类进行加载。另外如果有必要,还可以编写自定义的类加载器。这些类加载器的的关系一般如下图所示。
需要提示的是,上图的双亲委派模型中的各个类加载器之间并不表示继承关系,而是表示工作过程,具体说就是,对于一个加载类的具体请求,首先要委派给自己的父类去加载,只有当父类无法完成加载请求时,子类自己才会去尝试加载。具体的委派逻辑实现在java.lang.ClassLoader类的loadClass()方法中。loadClass()方法的实现如下:
源代码位置:java/lang/ClassLoader.java
protected Class<?> loadClass(Stringname,boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先从jvm缓存查找该类
Class c = findLoadedClass(name); // (1)
if (c ==null) {
try { //然后委托给父类加载器进行加载
if (parent !=null) {
c = parent.loadClass(name,false); (2)
} else { //如果父类加载器为null,则委托给启动类加载器加载
c = findBootstrapClassOrNull(name); (3)
}
} catch (ClassNotFoundException) {
// 如果父类加载器抛出ClassNotFoundException异常,
// 表明父类无法完成加载请求
}
if (c ==null) {
// 若仍然没有找到则调用findClass()方法查找
c = findClass(name); (4)
...
}
}
if (resolve) {
resolveClass(c); //(5)
}
return c;
}
}
类的加载流程如下图所示。
代码首先通过调用findLoadedClass()方法查找此类是否已经被加载过了,如果没有,则需要优先调用父类加载器去加载。除了用C++实现的引导类加载器需要通过调用findBootstrapClassOrNull()方法外,其它用Java实现的类加载器都有parent字段,因为这些类都继承了ClassLoader这个基类(这个类中有对parent字段的定义),如实现了扩展类加载器的ExtClassLoader类和实现了应用类加载器/系统类加载器的AppClassLoader类的继承关系如下图所示。
当父类无法实现加载请求时,也就是c为null时,当前类加载器调用findClass()方法尝试自己完成加载请求。
编写一个自定义的类加载器,如下:
实例1
package com.jvm;
import java.net.URL;
import java.net.URLClassLoader;
public class UserClassLoader extends URLClassLoader {
public UserClassloader(URL[] urls) {
super(urls);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return super.loadClass(name, resolve);
}
}
可以看到UserClassLoader继承了URLClassLoader类并覆写了loadClass()方法,调用super.loadClass()方法其实就是在调用ClassLoader类中实现的loadClass()方法。
实例1(续)
package com.jvm;
public class Student { }
实例1(续)
package com.jvm;
import java.net.URL;
public class TestClassLoader {
public static void main(String[] args) throws Exception {
URL url[] = new URL[1];
url[0] = Thread.currentThread().getContextClassLoader().getResource("");
UserClassLoader ucl = new UserClassLoader(url);
Class clazz = ucl.loadClass("com.jvm.Student");
Object obj = clazz.newInstance();
}
}
通过UserClassLoader类加载器加载Student类并通过调用Class.newInstance()方法获取Student对象。
下面详细介绍loadClass()方法中调用的findLoaderClass()、findBootstrapClassOrNull()与findClass()方法的实现。
调用的findLoadedClass()方法的实现如下:
protected final Class<?> findLoadedClass(String name) {
return findLoadedClass0(name);
}
调用本地方法findLoadedClass0()方法,这个方法的实现如下:
源代码位置:hotspot/src/share/vm/prims/jvm.cpp
JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
JVMWrapper("JVM_FindLoadedClass");
// THREAD表示当前线程
ResourceMark rm(THREAD);
Handle h_name (THREAD, JNIHandles::resolve_non_null(name));
// 获取类名对应的Handle
Handle string = java_lang_String::internalize_classname(h_name, CHECK_NULL);
// 检查是否为空
const char* str = java_lang_String::as_utf8_string(string());
if (str == NULL) return NULL;
// 判断类名是否超长
const int str_len = (int)strlen(str);
if (str_len > Symbol::max_length()) {
return NULL;
}
// 创建一个临时的Symbol
TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len, CHECK_NULL);
// 获取类加载器对应的Handle
Handle h_loader(THREAD, JNIHandles::resolve(loader));
// 查找目标类是否存在
Klass* k = SystemDictionary::find_instance_or_array_klass(klass_name,
h_loader,
Handle(),
CHECK_NULL);
// 将Klass转换成jclass
return (k == NULL) ? NULL :
(jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END
JVM_ENTRY是宏定义,用于处理JNI调用的预处理,如获取当前线程的JavaThread指针。由于还会涉及到JNI实现的一些相关规则,如JNIHandles::resolve_non_null()、JNIHandles::resolve()与JNIHandles::mark_local(),主要就是JNI函数不能直接访问Klass、oop对象只能借助jobject、jclass等来访问,为GC的处理提供便利,后面在介绍JNI时会详细介绍。
调用的find_instance_or_array_klass()函数的实现如下:
// Look for a loaded instance or array klass by name. Do not do any loading.
// return NULL in case of error.
Klass* SystemDictionary::find_instance_or_array_klass(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
Klass* k = NULL;
assert(class_name != NULL, "class name must be non NULL");
if (FieldType::is_array(class_name)) {
// The name refers to an array. Parse the name.
// dimension and object_key in FieldArrayInfo are assigned as a
// side-effect of this call
FieldArrayInfo fd;
BasicType t = FieldType::get_array_info(class_name, fd, CHECK_(NULL));
if (t != T_OBJECT) {
k = Universe::typeArrayKlassObj(t);
} else {
k = SystemDictionary::find(fd.object_key(), class_loader, protection_domain, THREAD);
}
if (k != NULL) {
k = k->array_klass_or_null(fd.dimension());
}
} else {
k = find(class_name, class_loader, protection_domain, THREAD);
}
return k;
}
其中有对数组和类的查询逻辑,方法并不涉及类的加载。对于数组的查找来说,首先要找到组成数组的基本元素的类型t,如果是基本类型,则调用Universe::typeArrayKlassObj()找到表示基本类型的Klass对象,在后面的类加载时会详细介绍;对于元素类型是对象来说,调用SystemDictionary::find()方法。SystemDictionary类中的find()方法的实现如下:
源代码位置:hotspot/src/share/vm/classfile/systemDictionary.cpp
Klass* SystemDictionary::find(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
...
class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));
ClassLoaderData* loader_data = ClassLoaderData::class_loader_data_or_null(class_loader());
...
unsigned int d_hash = dictionary()->compute_hash(class_name, loader_data);
int d_index = dictionary()->hash_to_index(d_hash);
{
...
return dictionary()->find(d_index, d_hash, class_name, loader_data,protection_domain, THREAD);
}
}
将已经加载的类存储在Dictionary中,为了加快查找采用了hash存储。只有类加载器和类才能确定唯一的表示Java类的Klass实例,所以在计算d_hash时必须传入class_name和loader_data这两个参数。计算出具体索引d_index后,就可以调用Dictionary类的find()方法进行查找了。调用的Dictionary::find()函数的实现如下:
源代码位置:hotspot/src/share/vm/classfile/dictionary.cpp
Klass* Dictionary::find(int index, unsigned int hash, Symbol* name,
ClassLoaderData* loader_data, Handle protection_domain, TRAPS) {
//根据类名和类加载器计算对应的klass在map里面对应的key
DictionaryEntry* entry = get_entry(index, hash, name, loader_data);
//存在,并且验证通过则返回
if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) {
return entry->klass();
} else {
//否者返回null,说明不存在
return NULL;
}
}
调用findBootstrapClassOrNull()方法请求引导类加载器完成加载请求,这个方法会调用本地方法findBootstrapClass()方法,源代码如下:
private Class<?> findBootstrapClassOrNull(String name){
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
这个本地方法的实现如下:
源代码位置:/src/share/native/java/lang/ClassLoader.c
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findBootstrapClass(JNIEnv *env, jobject loader,
jstring classname)
{
jclass cls = 0;
// ...
cls = JVM_FindClassFromBootLoader(env, clname);
// ...
return cls;
}
调用JVM_FindClassFromBootLoader()函数查找启动类加载器加载的类,如果没有查找到,方法会返回NULL。函数的实现如下:
源代码位置:hotspot/src/share/vm/prims/jvm.cpp
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))
JVMWrapper2("JVM_FindClassFromBootLoader %s", name);
// 检查类名是否合法
if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
return NULL;
}
// 调用SystemDictionary解析目标类,如果未找到返回null
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
if (k == NULL) {
return NULL;
}
// 将Klass转换成java中Class
return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END
调用的SystemDictionary::resolve_or_null()函数的实现如下:
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, TRAPS) {
return resolve_or_null(class_name, Handle(), Handle(), THREAD);
}
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {
// 数组,通过签名的格式来判断
if (FieldType::is_array(class_name)) {
return resolve_array_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL);
}
// 普通类,通过签名的格式来判断
else if (FieldType::is_obj(class_name)) {
ResourceMark rm(THREAD);
// Ignore wrapping L and ;.
TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
class_name->utf8_length() - 2,
CHECK_NULL);
return resolve_instance_class_or_null(name, class_loader, protection_domain, CHECK_NULL);
}
else {
return resolve_instance_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL);
}
}
调用resolve_array_class_or_null()方法查找数组时,如果组成数组元素的基本类型为引用类型,同样会调用resolve_instance_class_or_null()方法来查找类对应的Klass实例。方法的实现如下:
Klass* SystemDictionary::resolve_array_class_or_null(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
assert(FieldType::is_array(class_name), "must be array");
Klass* k = NULL;
FieldArrayInfo fd;
// dimension and object_key in FieldArrayInfo are assigned as a side-effect
// of this call
BasicType t = FieldType::get_array_info(class_name, fd, CHECK_NULL);
if (t == T_OBJECT) {
// naked oop "k" is OK here -- we assign back into it
Symbol* sb = fd.object_key();
k = SystemDictionary::resolve_instance_class_or_null(sb,
class_loader,
protection_domain,
CHECK_NULL);
if (k != NULL) {
k = k->array_klass(fd.dimension(), CHECK_NULL);
}
} else {
k = Universe::typeArrayKlassObj(t);
k = TypeArrayKlass::cast(k)->array_klass(fd.dimension(), CHECK_NULL);
}
return k;
}
包含对元素类型为引用类型和元素类型为基本类型的Klass实例的查找。基本类型的查找和find_instance_or_array_klass()方法的实现基本类似。下面看调用的resolve_instance_class_or_null()方法对引用类型的查找,实现如下:
Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
Handle class_loader,
Handle protection_domain,
TRAPS ){
// assert(name != NULL && !FieldType::is_array(name) &&
// !FieldType::is_obj(name), "invalid class name");
// UseNewReflection
// Fix for 4474172; see evaluation for more details
class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader()));
ClassLoaderData *loader_data = register_loader(class_loader, CHECK_NULL);
// Do lookup to see if class already exist and the protection domain has the right access
// This call uses find which checks protection domain already matches
// All subsequent calls use find_class, and set has_loaded_class so that
// before we return a result we call out to java to check for valid protection domain
// to allow returning the Klass* and add it to the pd_set if it is valid
// 在变量SystemDictionary::_dictionary中查找是否类已经加载,如果加载就直接返回
Dictionary* dic = dictionary();
// 通过类名和类加载器计算hash值,class_loader是Handle类型,而Handle._value的类型是oop*。而loader_data是ClassLoaderData*类型
unsigned int d_hash = dic->compute_hash(name, loader_data);
// 计算在hash中的索引位置
int d_index = dic->hash_to_index(d_hash);
// 根据hash和index 查到对应的klassOop
Klass* probe = dic->find(d_index, d_hash, name, loader_data,protection_domain, THREAD);
if (probe != NULL){
return probe; // 如果直接找到的话,则返回
}
// ... 省略其它查找的逻辑
}
如果类还没有加载,那么当前的方法还需要负责加载类。在实现的过程中考虑的因素比较多,比如解决并行加载、触发父类的加载、域权限的验证等,不过这些都不是我们要讨论的重点,我们仅看加载的过程,resolve_instance_class_or_null()方法中有如下调用:
// Do actual loading k = load_instance_class(name, class_loader, THREAD);
调用的方法如下:
// 体现出“双亲委派”机制,只要涉及到类的加载,都会调用这个函数
instanceKlassHandle SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {
instanceKlassHandle nh = instanceKlassHandle(); // null Handle
if (class_loader.is_null()) { // 使用引导类加载器来加载类
// Search the shared system dictionary for classes preloaded into the shared spaces.
// 在共享系统字典中搜索预加载到共享空间中的类,默认不使用共享空间,所以查找的结果为NULL
instanceKlassHandle k;
{
k = load_shared_class(class_name, class_loader, THREAD);
}
if (k.is_null()) {
// Use VM class loader,也就是系统类加载器进行类加载
k = ClassLoader::load_classfile(class_name, CHECK_(nh));
}
// find_or_define_instance_class may return a different InstanceKlass
// 调用SystemDictionary::find_or_define_instance_class->SystemDictionary::update_dictionary-> Dictionary::add_klass()将
// 生成的Klass对象存起来。Dictionary是个hash表实现,使用的也是开链法解决hash冲突。
if (!k.is_null()) {
// 支持并行加载,也就是允许同一个类加载器同时加载多个类
k = find_or_define_instance_class(class_name, class_loader, k, CHECK_(nh));
}
return k;
} else { // 使用指定的类加载器加载,最终会调用java.lang.ClassLoader()这个Java方法去执行类加载
// Use user specified class loader to load class. Call loadClass operation on class_loader.
ResourceMark rm(THREAD);
JavaThread* jt = (JavaThread*) THREAD;
Handle s = java_lang_String::create_from_symbol(class_name, CHECK_(nh));
// Translate to external class name format, i.e., convert ‘/‘ chars to ‘.‘
Handle string = java_lang_String::externalize_classname(s, CHECK_(nh));
JavaValue result(T_OBJECT);
KlassHandle spec_klass (THREAD, SystemDictionary::ClassLoader_klass());
// 调用java.lang.ClassLoader类中的loadClass()方法进行类加载
JavaCalls::call_virtual(&result,
class_loader,
spec_klass,
vmSymbols::loadClass_name(),
vmSymbols::string_class_signature(),
string,
CHECK_(nh));
// assert(result.get_type() == T_OBJECT, "just checking");
oop obj = (oop) result.get_jobject(); // 获取调用loadClass()方法返回的Class对象
// Primitive classes return null since forName() can not be
// used to obtain any of the Class objects representing primitives or void
if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {
// 获取Class对象表示的Java类,也就是获取表示Java类的instanceKlass对象
instanceKlassHandle k = instanceKlassHandle(THREAD, java_lang_Class::as_Klass(obj));
// For user defined Java class loaders, check that the name returned is
// the same as that requested. This check is done for the bootstrap
// loader when parsing the class file.
if (class_name == k->name()) {
return k;
}
}
// Class is not found or has the wrong name, return NULL
return nh;
}
}
当class_loader为NULL时,表示使用启动类加载器加载类,调用ClassLoader::load_classfile()方法加载类;当class_loader不为NULL时,会调用java.lang.ClassLoader类中的loadClass()方法,这在前面详细介绍过,这里不再介绍。
ClassLoader::load_classfile()方法的实现如下:
instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
ResourceMark rm(THREAD);
// Lookup stream for parsing .class file
ClassFileStream* stream = NULL;
int classpath_index = 0;
{
ClassPathEntry* e = _first_entry;
while (e != NULL) {
stream = e->open_stream(name, CHECK_NULL);
if (stream != NULL) {
break;
}
e = e->next();
++classpath_index;
}
}
instanceKlassHandle h;
if (stream != NULL) {
// class file found, parse it
ClassFileParser parser(stream);
ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data();
Handle protection_domain;
TempNewSymbol parsed_name = NULL;
// 解析Class文件
instanceKlassHandle result = parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h));
// add to package table
if (add_package(name, classpath_index, THREAD)) {
h = result;
}
}
return h;
}
方法首先从启动类加载器应该查找的路径下查找名称为name的Class文件,如果找到,调用parser.parseClassFile()方法解析Class文件,这个方法非常重要,后面在介绍解析Class文件的过程时会详细介绍。方法最后返回表示Java类的instanceKlass对象。
调用SystemDictionary::find_or_define_instance_class()方法可以支持并行加载,这个方法会调用SystemDictionary::update_dictionary()函数将已经加载的类添加到系统词典Map里面,如下:
源代码位置:hotspot/src/share/vm/classfile/systemDictionary.cpp
void SystemDictionary::update_dictionary(int d_index, unsigned int d_hash,
int p_index, unsigned int p_hash,
instanceKlassHandle k,
Handle class_loader,
TRAPS) {
// Compile_lock prevents systemDictionary updates during compilations
assert_locked_or_safepoint(Compile_lock);
Symbol* name = k->name();
ClassLoaderData *loader_data = class_loader_data(class_loader);
{
MutexLocker mu1(SystemDictionary_lock, THREAD);
...
// 当前对象已经存在了?
Klass* sd_check = find_class(d_index, d_hash, name, loader_data);
//不存在则添加
if (sd_check == NULL) {
//添加kclass到系统词典
dictionary()->add_klass(name, loader_data, k);
notice_modification();
}
...
}
其中key使用类的包路径+类名,类加载器两者确定,value则为具体加载的类对应的instanceKlassHandle对象,其中维护这kclass对象。也就是系统词典里面使用类加载器和类的包路径类名唯一确定一个类。这也验证了在Java中同一个类使用两个类加载器进行加载后,加载的两个类是不一样的,是不能相互赋值的。
调用findClass()方法完成类的加载请求,这个方法会调用本地函数defineClass1(),实现如下:
static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);
definClass1()对应的JNI方法为 Java_java_lang_ClassLoader_defineClass1(),实现如下:
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
jclass cls,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length,
jobject pd,
jstring source)
{
......
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
......
return result;
}
Java_java_lang_ClassLoader_defineClass1()函数主要是调用了JVM_DefineClassWithSource()加载类,最终调用的是 jvm.cpp 中的 jvm_define_class_common()函数。核心的实现逻辑如下:
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
jboolean verify, TRAPS) {
JavaThread* jt = (JavaThread*) THREAD;
// Since exceptions can be thrown, class initialization can take place
// if name is NULL no check for class name in .class stream has to be made.
TempNewSymbol class_name = NULL;
if (name != NULL) {
const int str_len = (int)strlen(name);
class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
}
ResourceMark rm(THREAD);
ClassFileStream st((u1*) buf, len, (char *)source);
Handle class_loader (THREAD, JNIHandles::resolve(loader));
Handle protection_domain (THREAD, JNIHandles::resolve(pd));
Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader, // 加载Java主类
protection_domain, &st,
verify != 0,
CHECK_NULL);
return (jclass) JNIHandles::make_local(env, k->java_mirror());
}
上面这段逻辑主要就是利用 ClassFileStream 将要加载的Class文件转成文件流,然后调用SystemDictionary::resolve_from_stream()函数生成 Class 在 HotSpot 中的表示Klass。resolve_from_stream()函数的实现如下:
// Add a klass to the system from a stream (called by jni_DefineClass and
// JVM_DefineClass).
// Note: class_name can be NULL. In that case we do not know the name of
// the class until we have parsed the stream.
Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
ClassFileStream* st,
bool verify,
TRAPS) {
// ...
// 解析文件流,生成 InstanceKlass
ClassFileParser cfp = ClassFileParser(st);
instanceKlassHandle k = cfp.parseClassFile(class_name,
loader_data,
protection_domain,
parsed_name,
verify,
THREAD);
// ...
if (!HAS_PENDING_EXCEPTION) {
// Add class just loaded
// If a class loader supports parallel classloading handle parallel define requests
// find_or_define_instance_class may return a different InstanceKlass
// 利用SystemDictionary注册生成的 Klass
// SystemDictionary 是用来帮助保存 ClassLoader 加载过的类信息的。准确点说,SystemDictionary
// 并不是一个容器,真正用来保存类信息的容器是 Dictionary,每个ClassLoaderData 中都保存着一个私有的
// Dictionary,而 SystemDictionary 只是一个拥有很多静态方法的工具类而已。
if (is_parallelCapable(class_loader)) {
k = find_or_define_instance_class(class_name, class_loader, k, THREAD);
} else {
// 如果禁止了并行加载,那么直接利用SystemDictionary将 InstanceKlass
// 注册到 ClassLoader的 Dictionary 中即可
define_instance_class(k, THREAD);
}
}
return k();
}
调用parseClassFile()完成类的解析,然后调用find_or_define_instance_class()或define_instance_class()方法完成在SystemDictionary中的注册。
实例2
更改实例1中的UserClassLoader类的loadClass()方法的实现,如下:
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("com.jvm")) {
return findClass(name);
}
return super.loadClass(name, resolve);
}
这样像Student这样的在com.jvm包下的类就会由用户自定义的类加载器UserClassLoader类来加载了。
更改实例1中TestClassLoader类的实现,使用Student类型来接收clazz.newInstance()获取到的Student对象,如下:
Student obj = (Student)clazz.newInstance();
实例运行后,抛出的异常的简要信息如下:
Exception in thread "main" java.lang.ClassCastException: com.jvm.Student cannot be cast to com.jvm.Student
因为实例化的Student对象所属的InstanceKlass是由UserClassLoader加载生成的,而我们要强转的类型Student对应的InstanceKlass是由系统默认的ClassLoader生成的,所以本质上它们就是两个毫无关联的InstanceKlass,当然不能强转。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
作者持续维护的个人博客classloading.com。
关注公众号,有HotSpot源码剖析系列文章!
原文:https://www.cnblogs.com/mazhimazhi/p/13338549.html