JVM——深入解析原理和运行机制(一)类加载器

      上次我们说了一下jvm中类加载的过程,大概有加载,连接(验证,准备,解析),初始化这么几个步骤,当然要实现这些功能就需要有加载器,今天我们就来说说jvm中的类加载器。

一、分类

      系统要想执行一个命令,就需要把所需要的资源加载到内存,然后再使用。java也是一样的,这就有了类加载器。类加载器主要分为启动类加载器(BootstrapClassLoader)、扩展类加载器(ExtensionClassLoader)和系统类加载器(SystemClassLoader ),另外还有上下文类加载器(ContextClassLoader)和自定义的类加载器。

1、BootstrapClassLoader

      启动类加载器引导类装入器是用本地代码实现的类装入器,它负责将 /lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

      启动类加载器是最低层的加载器,它是由C++编写的,因为加载器是一个类,也需要通过类加载器加载,启动类加载器就能完成这个功能。

2、ExtensionClassLoader

      扩展类加载器是由ExtClassLoader类实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

这里写图片描述

3、SystemClassLoader

      系统类加载器也成为应用程序类加载器,是由AppClassLoader类实现。它负责将系统类路径java -classpath或-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。

这里写图片描述

4、ContextClassLoader

      线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。
      而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

      Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

      这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。

      由此可见,线程上下文类加载器解决了SPI的类加载问题。

5、CustomClassLoader

      开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
      java.lang.ClassLoader 基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,即java.lang.Class类的一个实例。 除此之外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。

二、时机和机制

1、加载时机

  • 命令行启动应用时候由JVM初始化加载
  • 通过Class.forName()方法动态加载
  • 通过ClassLoader.loadClass()方法动态加载

2、机制

  • 全盘负责

      当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

  • 父类委托

      先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类

  • 缓存机制

      缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

三、双亲委派

      双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

这里写图片描述

      1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

      2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

      3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

      4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

总结:

      今天我们了解了一下类加载器的一些基础知识,知道双亲委派模型的实现,还有一点要注意的是java提供的上下文类加载器打破了这种双亲委派的模型,具体实现的方式不再多说,大家最好看看SPI的源码实现,理解了这个就懂的了类加载机制。

猜你喜欢

转载自blog.csdn.net/u010168160/article/details/78128474