专注虚拟机与编译器研究

第3.2篇-类的双亲委派机制

前面介绍了3种类加载器,每种类加载器都加载指定路径下的类库,它们在具体使用时并不是相互独立的,而是相互配合对类进行加载。另外如果有必要,还可以编写自定义的类加载器。这些类加载器的关系一般如下图所示。

 

需要提示的是,上图的各个类加载器之间并不表示继承关系,而是表示工作过程,具体说就是,对于一个加载类的具体请求,首先要委派给自己的父类加载器去加载,只有当父类加载器无法完成加载请求时,子类加载器自己才会去尝试加载,这就叫双亲委派。具体的委派逻辑在java.lang.ClassLoader类的loadClass()方法中实现。loadClass()方法的实现如下:

源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java

protected Class<?> loadClass(Stringname,boolean resolve) throws ClassNotFoundException {  
       synchronized (getClassLoadingLock(name)) {  
           // 首先从HotSpot VM缓存查找该类
           Class c = findLoadedClass(name); 
           if (c ==null) {  
               try {  // 然后委派给父类加载器进行加载
                   if (parent !=null) {  
                       c = parent.loadClass(name,false);  
                   } else {  // 如果父类加载器为null,则委派给启动类加载器加载
                       c = findBootstrapClassOrNull(name);  
                   }  
               } catch (ClassNotFoundException) {  
                   // 如果父类加载器抛出ClassNotFoundException异常,
                   // 表明父类无法完成加载请求
               }  

               if (c ==null) {  
                   // 当前类加载器尝试自己加载类
                   c = findClass(name);  
                   ...  
               }  
           }  
           ...
           return c;  
       }  
   }

类的加载流程如下图所示。

 

首先调用findLoadedClass()方法查找此类是否已经被加载过了,如果没有,优先调用父类加载器去加载。除了用C++实现的引导类加载器需要通过调用findBootstrapClassOrNull()方法加载外,其它用Java实现的类加载器一般都有parent字段(定义在java.lang.ClassLoader类中的字段),所以可直接调用parentloadClass()方法委派加载请求。除了引导类加载器外,其它加载器都继承了java.lang.ClassLoader这个基类,如实现了扩展类加载器的ExtClassLoader类和实现了应用类加载器的AppClassLoader类的继承关系如下图所示。

 

当父类无法完成加载请求时,也就是cnull时,当前类加载器调用findClass()方法尝试自己完成类加载的请求。

编写一个自定义的类加载器,如下:

实例1

package com.jvm;

import java.net.URL;
import java.net.URLClassLoader;

public class UserClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return super.loadClass(name, resolve);
    }
}

可以看到UserClassLoader继承了ClassLoader类并覆写了loadClass()方法,调用super.loadClass()方法其实就是在调用ClassLoader类中实现的loadClass()方法。

实例1(续)

package com.jvm;

public class Student { }

实例1(续)

package com.jvm;

public class TestClassLoader {
	public static void main(String[] args) throws Exception {
		UserClassLoader ucl = new UserClassLoader();
		Class clazz = ucl.loadClass("com.jvm.Student");

		Object obj = clazz.newInstance();
	}
}

通过UserClassLoader类加载器加载Student类并通过调用Class.newInstance()方法获取Student对象。

下面详细介绍loadClass()方法中调用的findLoaderClass()、findBootstrapClassOrNull()与findClass()方法的实现。

1.findLoadedClass()方法

findLoadedClass()方法调用本地函数findLoadedClass0()判断类是否已经加载。findLoadedClass()方法的实现如下:

protected final Class<?> findLoadedClass(String name) {
 return findLoadedClass0(name);
}

findLoadedClass0()函数的实现如下:

源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader,
                                           jstring name)
{
    if (name == NULL) {
        return 0;
    } else {
        return JVM_FindLoadedClass(env, loader, name);
    }
}

调用的JVM_FindLoadedClass()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp

JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
 
  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实例转换成java.lang.Class对象                                                         
  return (k == NULL) ? NULL :  (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

JVM_ENTRY是宏定义,用于处理JNI调用的预处理,如获取当前线程的JavaThread指针。因为垃圾回收等原因,JNI函数不能直接访问Klassoop实例,只能借助jobjectjclass等来访问,所以会调用JNIHandles::resolve_non_null()JNIHandles::resolve()JNIHandles::mark_local()等函数进行转换。

调用的SystemDictionary::find_instance_or_array_klass()函数的实现如下: 

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp
 
// 查找InstanceKlass或ArrayKlass实例,不进行任何类加载操作
Klass* SystemDictionary::find_instance_or_array_klass(
  Symbol*   class_name,
  Handle    class_loader,
  Handle    protection_domain,
  TRAPS
){ 
  Klass* k = NULL;
 
  if (FieldType::is_array(class_name)) { // 数组的查找逻辑
    FieldArrayInfo fd;
    // 获取数组的元素类型
    BasicType t = FieldType::get_array_info(class_name, fd, CHECK_(NULL));
    if (t != T_OBJECT) {  // 元素类型为Java基本类型
        k = Universe::typeArrayKlassObj(t);
    } else { // 元素类型为Java对象
        Symbol* sb = fd.object_key();
        k = SystemDictionary::find(sb, class_loader, protection_domain, THREAD);
    }
    if (k != NULL) {
        // class_name可能表示的是多维数组,所以可能需要根据维度创建出ObjArrayKlass实例
        k = k->array_klass_or_null(fd.dimension());
    }
  } else {  // 类的查找逻辑
      k = find(class_name, class_loader, protection_domain, THREAD);
  }
  return k;
}

如上函数包含了对数组和类的查询逻辑,方法并不涉及类的加载。如果是数组,首先要找到数组的元素类型t,如果是基本类型,调用Universe::typeArrayKlassObj()函数找到TypeArrayKlass实例,如果基本元素的类型是对象,调用SystemDictionary::find()方法从字典中查找InstanceKlass实例,所有已加载的InstanceKlass实例都会存储到字典中。

查找对象的SystemDictionary::find()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.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_nameloader_data这两个参数。计算出具体索引d_index后,就可以调用Dictionary类的find()函数进行查找了。调用的Dictionary::find()函数的实现如下: 

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp

Klass* Dictionary::find(
 int index, 
 unsigned 
 int hash, 
 Symbol* name,
 ClassLoaderData* loader_data, 
 Handle protection_domain, 
 TRAPS
) {
  // 根据类名和类加载器计算对应的Klass实例在字典里的存储的key
  DictionaryEntry* entry = get_entry(index, hash, name, loader_data);
  if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) {
    return entry->klass();
  } else {
    return NULL;
  }
}

调用get_entry()函数从hash表中查找Klass实例,如果找到并且验证是合法的,则返回Klass实例,否则返回NULL

findLoadedClass()方法执行的流程如下图所示。

2.findBootstrapClassOrNull()方法

调用findBootstrapClassOrNull()方法请求引导类加载器完成类的加载请求,这个方法会调用本地函数findBootstrapClass(),此函数的实现如下:

源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java

private Class<?> findBootstrapClassOrNull(String name){
    return findBootstrapClass(name);
}
 
private native Class<?> findBootstrapClass(String name);

这个本地函数在HotSpot VM中的实现如下: 

源代码位置:openjdk/jdk/src/share/native/java/lang/ClassLoader.c 
 
JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_findBootstrapClass(
   JNIEnv     *env, 
   jobject    loader,
   jstring    classname
){
    char *clname;
    jclass cls = 0;
    char buf[128];

    if (classname == NULL) {
        return 0;
    }

    clname = getUTF(env, classname, buf, sizeof(buf));
    ...
    cls = JVM_FindClassFromBootLoader(env, clname);
    ...
    return cls;
}

调用JVM_FindClassFromBootLoader()函数查找启动类加载器加载的类,如果没有查到,函数会返回NULL。函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp

JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))
  // 检查类名是否合法
  if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
    return NULL;
  }
   
  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
  // 调用SystemDictionary.resolve_or_null()函数解析目标类,如果未找到,返回NULL
  Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
  if (k == NULL) {
    return NULL;
  }
  // 将Klass实例转换成java.lang.Class对象
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

 

调用SystemDictionary::resolve_or_null()函数对类进行查找,函数的实现如下: 

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp

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)) {
    // 去掉签名中的开头字符L和结束字符;
    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实例。函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp

Klass* SystemDictionary::resolve_array_class_or_null(
   Symbol*   class_name,
   Handle    class_loader,
   Handle    protection_domain,
   TRAPS
){
  Klass*          k = NULL;
  FieldArrayInfo  fd;
  // 获取数组元素的类型
  BasicType t = FieldType::get_array_info(class_name, fd, CHECK_NULL);
  if (t == T_OBJECT) { // 数组元素的类型为对象
    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 { // 数组元素的类型为Java基本类型
    k = Universe::typeArrayKlassObj(t);
    int x = fd.dimension();
    TypeArrayKlass* tak = TypeArrayKlass::cast(k);
    k = tak->array_klass(x, CHECK_NULL);
  }
  return k;
}

对元素类型为对象类型和元素类型为基本类型的一维数组的Klass实例进行查找。查找基本类型的一维数组和find_instance_or_array_klass()函数的实现类似。下面看调用的resolve_instance_class_or_null()函数对对象类型的查找,实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp
 
Klass* SystemDictionary::resolve_instance_class_or_null(
   Symbol*   name,
   Handle    class_loader,
   Handle    protection_domain,
   TRAPS 
){
  // 在变量SystemDictionary::_dictionary中查找是否类已经加载,如果加载就直接返回
  Dictionary* dic = dictionary();
  // 通过类名和类加载器计算hash值
  unsigned int d_hash = dic->compute_hash(name, loader_data);
  // 计算在hash表中的索引位置
  int d_index = dic->hash_to_index(d_hash);
  // 根据hash和index 查到对应的Klass实例
  Klass* probe = dic->find(d_index, d_hash, name, loader_data,protection_domain, THREAD);
  if (probe != NULL){
       return probe; // 如果从字典中找到就直接返回
  }
  ...  
  // 从字典中没有找到时,需要对类进行加载
  if (!class_has_been_loaded) {
      k = load_instance_class(name, class_loader, THREAD);
      ...
  }
  ...
}

如果类还没有加载,那么当前的函数还需要负责加载类。在实现的过程中考虑的因素比较多,比如解决并行加载、触发父类的加载、域权限的验证等,不过这些都不是要讨论的重点。当类没有加载时,调用load_instance_class()函数进行加载。

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp

// 体现出“双亲委派”机制,只要涉及到类的加载,都会调用这个函数
instanceKlassHandle SystemDictionary::load_instance_class(
Symbol* class_name, 
Handle class_loader, TRAPS
) {
  instanceKlassHandle nh = instanceKlassHandle(); // 空的Handle
  if (class_loader.is_null()) { // 使用引导类加载器来加载类
 
    // 在共享系统字典中搜索预加载到共享空间中的类,默认不使用共享空间,所以查找的结果为NULL
    instanceKlassHandle k;
    {
      k = load_shared_class(class_name, class_loader, THREAD);
    }
 
    if (k.is_null()) {
      // 使用引导类加载器进行类加载
      k = ClassLoader::load_classfile(class_name, CHECK_(nh));
    }
    // 调用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;
  } 
  // 使用指定的类加载器加载,最终会调用java.lang.ClassLoader类中的loadClass()方法执行类加载
  else {

    JavaThread* jt = (JavaThread*) THREAD;
 
    Handle s = java_lang_String::create_from_symbol(class_name, CHECK_(nh));
    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));

   // 获取调用loadClass()方法返回的java.lang.Class对象
   oop obj = (oop) result.get_jobject();
 
    // 调用loadClass()方法加载的必须是对象类型
    if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {
      // 获取java.lang.Class对象表示的Java类,也就是获取表示Java类的instanceKlass实例
      instanceKlassHandle k = instanceKlassHandle(THREAD, java_lang_Class::as_Klass(obj));
 
      if (class_name == k->name()) {
        return k;
      }
    }
    // Class文件不存在或名称错误,返回空的Handle实例
    return nh;
  }
}

class_loaderNULL时,表示使用启动类加载器加载类,调用ClassLoader::load_classfile()函数加载类;当class_loader不为NULL时,会调用java.lang.ClassLoader类中的loadClass()方法加载。这种判断逻辑也是双亲委派逻辑的体现。

findBootstrapClassOrNull()方法执行的流程如下图所示。

使用引导类加载器加载类时,调用ClassLoader::load_classfile()函数加载类,如果得到了Klass实例,随后调用的SystemDictionary::find_or_define_instance_class()函数会将这个Klass实例添加到字典中。函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp

instanceKlassHandle SystemDictionary::find_or_define_instance_class(
    Symbol*               class_name,
    Handle                class_loader,
    instanceKlassHandle   k,
    TRAPS
) {
 
  instanceKlassHandle  nh = instanceKlassHandle(); // 空的Handle
  Symbol*              name_h = k->name(); 
  ClassLoaderData*     loader_data = class_loader_data(class_loader);
 
  unsigned int   d_hash = dictionary()->compute_hash(name_h, loader_data);
  int  d_index = dictionary()->hash_to_index(d_hash);
 
   ...
  {
    MutexLocker mu(SystemDictionary_lock, THREAD);
    // 检查一下类是否已经加载过了,如果已经加载过,则存在对应的InstanceKlass实例
    if (UnsyncloadClass || (is_parallelDefine(class_loader))) {
      Klass* check = find_class(d_index, d_hash, name_h, loader_data);
      if (check != NULL) {
        return(instanceKlassHandle(THREAD, check));
      }
    } 
    ...
  }
  // 在SystemDictionary::load_instance_class()函数里已经调用ClassLoader::load_classfile()
  // 函数加载了类,所以这里只需要创建InstanceKlass实例并保存到字典中即可
  define_instance_class(k, THREAD); 
  ... 
  return k;
}

函数同样会调用find_class()函数从字典中检查一下这个类是否已经保存到系统字典中了(因为并行加载的原因,其它线程可能已经创建了InstanceKlass实例并保存到字典中了),如果找到实例就直接返回,否则调用define_instance_class()函数定义一个InstanceKlass实例并保存到字典中,此函数会最终调用SystemDictionary::update_dictionary()函数将已经加载的类添加到系统词典SystemDictionary里面,如下:

源代码位置:openjdk/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
) {
  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) {
          dictionary()->add_klass(name, loader_data, k);
      }
  }
}

SystemDictionary用来保存类加载器加载过的类信息。准确点说,SystemDictionary并不是一个容器,真正用来保存类信息的容器是Dictionary,每个ClassLoaderData中都保存着一个私有的Dictionary,而SystemDictionary只是一个拥有很多静态函数的工具类而已。

Dictionary的底层数据存储结构为hashkey使用类的包路径+类名和类加载器两者确定,value则为具体加载的类对应的instanceKlassHandle实例。所以在系统词典里面使用类加载器和类的包路径+类名唯一确定一个类。这也验证了在Java中同一个类使用两个类加载器进行加载后,加载的两个类是不一样的,是不能相互赋值的。

3.findClass()方法

调用findClass()方法完成类的加载请求,这个方法会调用本地函数defineClass1()definClass1()对应的本地方法为Java_java_lang_ClassLoader_defineClass1(),实现如下:

源代码位置:openjdk/jdk/src/share/native/java/lang/ClassLoader.c
 
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_define_class_common()函数。核心的实现逻辑如下:

源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp
 
JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env,const char *name,
        jobject loader,const jbyte *buf,jsize len,jobject pd,const char *source
))
 
  return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD);
JVM_END
 
 
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
) {
 
  TempNewSymbol class_name = NULL;
  // 在HotSpot VM中,字符串使用Symbol实例表示,以达到重用和唯一的目的
  if (name != NULL) {
    const int str_len = (int)strlen(name);
    class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
  }
 
  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,
protection_domain, &st,verify != 0,CHECK_NULL);
 
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
}

利用ClassFileStream将要加载的Class文件转成文件流,然后调用SystemDictionary::resolve_from_stream()函数生成Klass实例。函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp

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) {
    if (is_parallelCapable(class_loader)) { // 支持并行加载
      k = find_or_define_instance_class(class_name, class_loader, k, THREAD);
    } else {
      // 如果禁止了并行加载,那么直接利用SystemDictionary将InstanceKlass实例
      // 注册到SystemDictionary中
      define_instance_class(k, THREAD);
    }
  }
 
  return k();
}

调用parseClassFile()函数完成类的解析之后会生成Klass实例,调用find_or_define_instance_class()define_instance_class()函数将Klass实例注册到SystemDictionary中。关于parseClassFile()函数将在第4章详细介绍。

findClass()方法的执行流程如图3-5所示。

SystemDictionary 是用来帮助保存 ClassLoader 加载过的类信息的。准确点说,SystemDictionary 并不是一个容器,真正用来保存类信息的容器是 Dictionary,每个ClassLoaderData 中都保存着一个私有的 Dictionary,而 SystemDictionary 只是一个拥有很多静态方法的工具类而已。

如上省略了一些逻辑,如支持并行加载等逻辑。如果允许并行加载,那么前面就不会对 ClassLoader 加锁,所以在同一时刻,可能对同一 Class 文件加载了多次。

但是同一 Class 在同一 ClassLoader 中必须保持唯一性,所以这里会先利用 SystemDictionary 查询 ClassLoader 是否已经加载过相同 Class。

如果已经加载过,那么就将当前线程刚刚加载的 InstanceKlass 加入待回收列表,并将 InstanceKlass* k 重新指向利用 SystemDictionary 查询到的 InstanceKlass。如果没有查询到,那么就将刚刚加载的 InstanceKlass 注册到 ClassLoader 的 Dictionary 中。 虽然并行加载不会锁住 ClassLoader ,但是会在注册 InstanceKlass 时对 SystemDictionary 加锁,所以不需要担心 InstanceKlass 在注册时的并发操作。如果禁止了并行加载,那么直接利用 SystemDictionary 将 InstanceKlass 注册到 ClassLoader 的 Dictionary 中即可。

参考:https://zhuanlan.zhihu.com/p/60328095

实例2

更改实例1中的UserClassLoader类的loadClass()方法的实现,如下:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    if (name.startsWith("com.classloading")) {
        return findClass(name);
    }
    return super.loadClass(name, resolve);
}

这样像Student这样的在com.classloading包下的类就会由用户自定义的类加载器UserClassLoader类来加载了。

更改实例TestClassLoader类的实现,使用Student类型来接收clazz.newInstance()获取到的Student对象,如下:

Student obj = (Student)clazz.newInstance();

实例运行后,抛出的异常的简要信息如下:

Exception in thread "main" java.lang.ClassCastException: com.classloading.Student cannot be cast to com.classloading.Student

因为实例化的Student对象所属的InstanceKlass实例是由UserClassLoader加载生成的,而我们要强制转换的Student类型对应的InstanceKlass实例是由系统默认的AppClassLoader生成的,所以本质上它们是两个毫无关联的InstanceKlass实例,当然不能强制转换。

参考文章:

(1)类加载器的实现

(2)类的预加载 

(3)Java类的加载

 

posted on 2020-07-19 07:38  鸠摩(马智)  阅读(1368)  评论(3编辑  收藏  举报

导航