【Hotspot】类生命周期(1):类加载 Bootstrap 以及双亲委托

本文详细介绍了Java的类加载器体系,包括启动类加载器、扩展类加载器和应用类加载器,以及双亲委派模型的工作原理。类加载过程涉及到BootstrapClassLoader的C++实现,以及OOP-Klass模型在HotSpotJVM中的应用。同时,文章阐述了如何调用BootstrapClassLoader以及类加载器如何处理类的加载请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.类加载器的分类
(1)启动类加载器/引导类加载器(Bootstrap ClassLoader):
这个ClassLoader类是用C++/C语言编写的,负责将<JAVA_HOME>/lib目录、-Xbootclasspath选项指定的目录或系统属性sun.boot.class.path指定的目录下的核心类库加载到内存中。
源码文件是 openjdk/hotspot/src/share/vm/classfile/classLoader.hpp
(2)扩展类加载器(Extension ClassLoader)
扩展类加载器由sun.misc.Launcher$ExtClassLoader类 Java 实现,负责将<JAVA_HOME >/lib/ext目录或者由系统变量-Djava.ext.dir所指定的目录中的类库加载到内存中。
源文件是 openjdk/jdk/src/share/classes/sun/misc/Launcher.java 内部类 $ExtClassLoader。
(3)应用类加载器(Application ClassLoade)
应用类加载器由sun.misc.Launcher$AppClassLoader类 Java 实现,负责将由系统环境变量-classpath、-cp或系统属性java.class.path指定的路径下的类库加载到内存中。
源文件是 openjdk/jdk/src/share/classes/sun/misc/Launcher.java 内部类 $AppClassLoader。
2.双亲委派是怎么实现的
Java虚拟机对class文件采取的是按需加载的方式,也就是说需要使用该类时才会将它的class文件加载到内存生成class对象。
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
如果父类加载器还存在其父类加载器,则进一步向上委托,以此递归请求然后达到顶层的启动类加载器;
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
按照优先级从上倒下,可以防止类的重复加载。
除了顶层C++实现的 Bootstrap ClassLoader 之外,Java 实现的类加载器都是从 java.lang.ClassLoader 继承过来。
子类实际上是扩展定义了类加载目录,最终依然是调用父类的类加载方法。
代码如下:
 java.lang.ClassLoader
  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    synchronized (getClassLoadingLock(name)) {
    // 判断缓存中是否已经存在这个类
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        long t0 = System.nanoTime();
        try {
            // 如果 parent 不为空则调用父类的 loadClass()
            if (parent != null) {
                c = parent.loadClass(name, false);
            }
            // 如果 parent 为空则调用 native 方法,使用 C++ 实现的 Bootstrap ClassLoader 进行加载
            else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {}
        // 如果还是未加载,则交给重写的 findClass() 进行加载,这个也就是自定义加载实现
        if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            long t1 = System.nanoTime();
            c = findClass(name);
            sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
            sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
            sun.misc.PerfCounter.getFindClasses().increment();
        }
    }
    if (resolve) {
        resolveClass(c);
    }
        return c;
    }
}

也就是说  $ExtClassLoader、 $AppClassLoader 两种类加载器,都会调用父类的  ClassLoader.loadClass() 来判断自己是否有 parent 父亲加载器,有的话向上传,没有的话直接调用  Bootstrap ClassLoader。
这个 parent 通过加载器的构造函数进行设置,这样得到了上图的加载优先级:
ExtClassLoader(File[] dirs) throws IOException {
    super(getExtURLs(dirs), null, factory); // 为parent字段传递的参数为null,类加载会交给 Bootstrap ClassLoader。
}
    
AppClassLoader(URL[] urls, ClassLoader parent) {
    super(urls, parent, factory);  // parent通常是 ExtClassLoader 对象,类加载会交给 ExtClassLoader 。
}

也就是说最终的类加载还是由 Bootstrap ClassLoader 进行加载,加载传递顺序为:
Application ClassLoader    =>    
Extension ClassLoader    =>     
Bootstrap ClassLoader
但是还有另外一个问题,那就是Java类加载顶层父类  java.lang.ClassLoader.loadClass() 除了  loadClass() 之外还有 findClass() 函数,这两个有什么区别的?
实际上就是因为顶层父类 loadClass() 实现了双亲委派,默认的子类AppLoader、Extloader都 没有去重写这个方法,而是重写的 findClass()。
如果用户自定义类加载器需要延用双亲委派机制的话,就应该这样写,在继承的 loadClass() 中依然调用顶层父类:
public class UserClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return super.loadClass(name, resolve);
    }
}

如果不按照上面你的写法那么用户重写的类加载函数就会打破双亲委派,不能为了强制用户写出的代码遵循双亲委派模型而不允许重写loadClass(name)方法。所以定义 findClass(name) 让用户进行重写自己的类加载。
而 findClass() 方法会在 loadClass 尝试使用父加载器无法加载时进行调用,这样在最后调用用户自定义类加载函数进行加载:
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 加载指定类路径,调用 findClass()
    if (name.startsWith("com.classloading")) {
        return findClass(name);
    }
    return super.loadClass(name, resolve);
}

3.如何调用  Bootstrap ClassLoader
触发上诉类加载的时机一般是:
(1)加载 Java 静态 main 函数所在的主类。
(2)new一个类的对象,调用类的静态成员(除了由final修饰的常量外)和静态方法,无论是在解析执行还是编译执行的情况下,都会在处理new、getstatic、putstatic 或invokestatic字节码指令时对类进行初始化。
(3)使用java.lang.reflect包的方法对类进行反射调用的时候,如果类还没有初始化过,则需要进行初始化。
前面   java.lang.ClassLoader 中的 loadclass() 函数中,有两个方法调用了 native 函数:
findLoadedClass()    =>    native 方法 findLoadedClass0() 判断缓存中是否已经存在这个类
findBootstrapClassOrNull()    =>    native 方法 findBootstrapClass() 使用 Bootstrap ClassLoader 进行加载
其中的 native findBootstrapClass() 就是从 Java 转换到 C++ 类加载器的入口,调用顺序如下:
java.lang.ClassLoader findBootstrapClassOrNull()    =>    parent为空,请求 Bootstrap ClassLoader 进行加载
java.lang.ClassLoader findBootstrapClass()    => 调用 native 函数,这个函数绑定的是 c 语言的 ClassLoader.c 中的函数
接下来就交给hotspot实现:
其中函数调用流程:
jdk/src/share/native/java/lang Java_java_lang_ClassLoader_findBootstrapClass()     =>    本地实现类加载入口
hotspot/src/share/vm/classfile/systemDictionary.cpp resolve_instance_class_or_null()    =>    在变量SystemDictionary::_dictionary中查找是否类已经加载,从字典中没有找到时,需要对类进行加载
hotspot/src/share/vm/classfile/systemDictionary.cpp load_instance_class()     =>    从字典中没有找到时,需要对类进行加载
hotspot/src/share/vm/classfile/classLoader.cpp ClassLoader::load_classfile()     =>    使用引导类加载器进行类加载

虚拟机将字节码文件以流的形式加载到内存中,再将字节码文件各信息组合成虚拟机需要的对象模型和内存模型存储到MateSpace中。
最后初始化对象模型,以备使用,依照对象模型,虚拟机可以按指令在堆中创建无数有限个对象。
会在 SystemDictionary::_dictionary中查找是否类已经加载,从字典中没有找到时,需要对类进行加载。
SystemDictionary::resolve_instance_class_or_null() 如下:
Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name,
                                                        Handle class_loader,
                                                        Handle protection_domain,
                                                        TRAPS) {
  // 通过类名和类加载器计算hash值,再将hash值转换为index
  // 再 dictionary() 中查找是否有这个index的类,类加载信息是服装城 Klass 对象的
  unsigned int d_hash = dictionary()->compute_hash(name, loader_data);
  int d_index = dictionary()->hash_to_index(d_hash);
  Klass* probe = dictionary()->find(d_index, d_hash, name, loader_data,protection_domain, THREAD);
  if (probe != NULL) return probe;
    // 省略
    if (!class_has_been_loaded) {
      // 进行类加载,并返回 Klass 对象
      k = load_instance_class(name, class_loader, THREAD);
      // 省略
    }
    // 省略
  return k();
}

接着调用引导类加载器进行加载后,将得到的 Klass 对象缓存到 SystemDictionary::_dictionary中:
instanceKlassHandle SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {
  instanceKlassHandle nh = instanceKlassHandle(); // null Handle
  // 默认的加载方式
  if (class_loader.is_null()) {
    // 省略
      // 使用引导类加载器进行类加载
    if (k.is_null()) {
      // Use VM class loader
      PerfTraceTime vmtimer(ClassLoader::perf_sys_classload_time());
      k = ClassLoader::load_classfile(class_name, CHECK_(nh));
    }
    // 将 Klass实例添加到字典中
    if (!k.is_null()) {
      k = find_or_define_instance_class(class_name, class_loader, k, CHECK_(nh));
    }
    return k;
  }
  // 使用指定的类加载器加载,最终会调用java.lang.ClassLoader类中的loadClass()方法执行类加载
  else {
    // 省略
    return nh;
  }
}

4. Oop和Klass
前面可以看出类加载缓存是封装成 Klass 保存在 SystemDictionary::_dictionary 中的, OOP-Klass 模型概述:
HotSpot JVM 并没有根据 Java 实例对象直接通过虚拟机映射到新建的 C++ 对象,而是设计了一个 oop-klass 模型。
oop 指的是 Ordinary Object Pointer(普通对象指针,指向被创建的对象,具体通过什么结构创建对象请向下看…),它用来表示对象的实例信息,看起来像个指针实际上是藏在指针里的对象;而 klass 则包含元数据和方法信息,用来描述 Java 类。
为何要设计这样一个一分为二的对象模型呢?这是因为 HotSopt JVM 的设计者不想让每个对象中都含有一个 vtable(虚函数表),所以就把对象模型拆成 klass 和 oop,其中 oop 中不含有任何虚函数,而 klass 就含有虚函数表,可以进行 method dispatch。
这个模型其实是参照的 Strongtalk VM 底层的对象模型。
JVM在运行时,需要一种用来标识Java内部类型的机制。HotSpot的解决方案是,为每一个已加载的Java类创建一个instanceKlass对象,用来在JVM层表示Java类。
一个 Klass 对象代表一个类的元数据。klass体系如下:
class  Klass;//klassOop的一部分,用来描述语言层的类型
class   instanceKlass;//在虚拟机层面描述一个Java类
class     instanceMirrorKlass;//专有instantKlass,表示java.lang.Class的Klass
class     instanceRefKlass;//专有instantKlass,表示java.lang.ref.Reference的子类的Klass
class   methodKlass;//表示methodOop的Klass
class   constMethodKlass;//表示constMethodOop的Klass
class   methodDataKlass;//表示methodDataOop的Klass
class   klassKlass;//最为klass链的端点,klassKlass的Klass就是它自身
class     instanceKlassKlass;//表示instanceKlass的Klass
class     arrayKlassKlass;//表示arrayKlass的Klass
class       objArrayKlassKlass;//表示objArrayKlass的Klass
class       typeArrayKlassKlass;//表示typeArrayKlass的Klass
class   arrayKlass;//表示array类型的抽象基类
class     objArrayKlass;//表示objArrayOop的Klass
class     typeArrayKlass;//表示typeArrayOop的Klass
class   constantPoolKlass;//表示constantPoolOop的Klass
class   constantPoolCacheKlass;//表示constantPoolCacheOop的Klass

而其中 instanceKlass 就用来表示一个已加载的 Java 类:
  objArrayOop     _methods;  //类拥有的方法列表
  typeArrayOop    _method_ordering;  //描述方法顺序
  objArrayOop     _local_interfaces;  //实现的接口
  objArrayOop     _transitive_interfaces;  //继承的接口
  typeArrayOop    _fields;  //域
  constantPoolOop _constants;  //常量
  oop             _class_loader;  //类加载器
  oop             _protection_domain;  //protected域
  ....

类数据这样的一个C++结构体实例也是一个对象,这个对象存在在方法区里。但是这个类数据对象即 InstanceKlass 是给VM内部用的,并不直接暴露给 Java 层。
在 HotSpot VM 里,java.lang.Class 的实例被称为“Java mirror”,意思是它是 VM 内部用的 klass 对象的“镜像”;
作用是把 InstanceKlass 对象包装了一层来暴露给 Java 层(开发者)使用,但主要用途是提供反射访问;
创建时机是加载->连接->初始化 的初始化阶段。
每个 Java 对象的对象头里,_klass 字段会指向一个VM内部用来记录类的元数据用的 InstanceKlass 对象
insanceKlass里有个 _java_mirror 字段(见下图),指向该类所对应的Java镜像——java.lang.Class实例
另外,HotSpot VM会给 Class 对象注入一个隐藏字段“klass”,用于指回到其对应的 InstanceKlass 对象。这样,klass与mirror之间就有双向引用:
5.类加载器 Bootstrap ClassLoader
也就是 ClassLoader::load_classfile() 这个函数。
instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
  ResourceMark rm(THREAD);
    // 获取类名
  const char* class_name = h_name->as_C_string();
  EventMark m("loading class %s", class_name);
  ThreadProfilerMark tpm(ThreadProfilerMark::classLoaderRegion);
  stringStream st;
  // st.print() uses too much stack space while handling a StackOverflowError
  // st.print("%s.class", h_name->as_utf8());
  st.print_raw(h_name->as_utf8());
  st.print_raw(".class");
  const char* file_name = st.as_string();
  ClassLoaderExt::Context context(class_name, file_name, THREAD);
  // Lookup stream for parsing .class file
    // ClassFileStream表示Class文件的字节流
  ClassFileStream* stream = NULL;
  int classpath_index = 0;
  ClassPathEntry* e = NULL;
  instanceKlassHandle h;
  {
    PerfClassTraceTime vmtimer(perf_sys_class_lookup_time(),
                               ((JavaThread*) THREAD)->get_thread_stat()->perf_timers_addr(),
                               PerfClassTraceTime::CLASS_LOAD);
      //从第一个ClassPathEntry开始遍历所有的ClassPathEntry,也就是从多个类路径下进行查找
    e = _first_entry;
    while (e != NULL) {
      stream = e->open_stream(file_name, CHECK_NULL);
        // 如果检查返回false则返回null,check()函数默认返回true
      if (!context.check(stream, classpath_index)) {
        return h; // NULL
      }
        // 如果找到目标文件则跳出循环
      if (stream != NULL) {
        break;
      }
      e = e->next();
      ++classpath_index;
    }
  }
    //如果找到了目标Class文件
  if (stream != NULL) {
    // class file found, parse it
      // 构建一个ClassFileParser实例用于 parse 解析
    ClassFileParser parser(stream);
      // 构建一个ClassLoaderData实例
    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,
                                                       context.should_verify(classpath_index),
                                                       THREAD);
    if (HAS_PENDING_EXCEPTION) {
      ResourceMark rm;
      if (DumpSharedSpaces) {
        tty->print_cr("Preload Error: Failed to load %s", class_name);
      }
      return h;
    }
      // 调用ClassLoader的add_package函数,把当前类的包名加入到_package_hash_table中,防止重复加载
    h = context.record_result(classpath_index, e, result, THREAD);
  } else {
    if (DumpSharedSpaces) {
      tty->print_cr("Preload Warning: Cannot find %s", class_name);
    }
  }
  return h;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值