类加载机制ClassLoader简介

  ClassLoader在Java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入系统,然后交给Java虚拟机进行连接、初始化等操作。因此,ClassLoader在整个装载阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的连接和初始化行为。
  从代码层次看,ClassLoader是一个抽象类,它提供了一些重要的接口,用于自定义Class的加载流程和加载方式。ClassLoader的主要方法如下:

  • public Class loadClass(String name) throws ClassNotFoundException

    给定一个类名,加载一个类,返回代表这个类的Class实例,如果找不到类,则返回ClassNotFoundException异常

  • protected final Class defineClass(byte[] b,int off,int len)

    根据给定的字节码流b定义一个类,off和len参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的。这是受保护的方法,只有在自定义ClassLoader子类中可以使用

  • protected Class findClass(String name) throws ClassNotFoundException

    查找一个类,这是一个受保护的方法,也是重载ClassLoader时,重要的系统扩展点。这个方法会在loadClass()时被调用,用于自定义查找类的逻辑。如果不需要修改类加载默认机制,只是想改变类加载的形式就可以重载该方法

  • protected final Class findLoadedClass(String name)

    这也是一个受保护的方法,它会去寻找已经加载的类。这个方法是final方法,无法被修改

  在ClassLoader的结构中,还有一个重要的字段parent,它也是一个ClassLoader的实例,这个字段所表示的ClassLoader也称为这个ClassLoader的双亲。在类加载的过程中,ClassLoader可能会将某些请求交予自己的双亲处理。

ClassLoader的分类

&nbap; 在标准的Java程序中,Java虚拟机会创建3类ClassLoader为整个应用程序服务。它们分别是:BootStrapClassLoader(启动类加载器)、ExtensionClassLoader(扩展类加载器)和AppClassLoader(应用类加载器,也称为系统类加载器)。此外,每个应用程序还可以拥有自定义的ClassLoader,扩展Java虚拟机获取Class数据的能力。

  各个ClassLoader的层次和功能如下图所示,从ClassLoader的层次自顶往下为启动类加载器、扩展类加载器、应用类加载器和自定义类加载器。其中,应用类加载器的双亲为扩展类加载器,扩展类加载器的双亲为启动类加载器。当系统需要使用一个类时,在判断类是否已经被加载时,会先从当前底层类加载器进行判断。当系统需要加载一个类时,会从顶层类开始加载,依次向下尝试,直到成功。

类加载机制ClassLoader简介_第1张图片

  在这些ClassLoader中,启动类加载器最为特别,它是完全由C代码实现的,并且在Java中没有对象与之对应。系统的核心类就是由启动类加载器进行加载的,它也是虚拟机的核心组件。扩展类加载器和应用类加载器都有对应的Java对象可供使用。

  无法在Java代码中直接访问启动类加载器,因为这是一个纯C实现,因此任何加载在启动类加载器中的类是无法获得其ClassLoader实例的,比如:

String.class.getClassLoader()

由于String属于Java核心类,由启动类加载器加载,故以上代码返回的是null。

ClassLoader的双亲委托模式

  系统中的ClassLoader在协同工作时,默认会使用双亲委托模式。即在类加载的时候,系统会判断当前类是否已经被加载,如果已经被加载,就会直接返回可用的类,否则就会尝试加载,在尝试加载时,会先请求双亲处理,如果双亲请求失败,则会自己加载。

注意:双亲为null有两种情况:第一,其双亲就是启动类加载器;第二,当前加载器就是启动类加载器。判断类是否加载时,应用类加载器会顺着双亲路径往上判断,直到启动类加载器。但是启动类加载器不会往下询问,这个委托是单向的。

双亲委托模式的弊端

  前文提到,检查类是否加载的委托过程是单向的,这个方式虽然从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader所加载的类。

  通常情况下,启动类加载器中的类为系统核心类,包括一些重要的系统接口,而在应用类加载器中,为应用类。按照这种模式,应用类访问系统类自然是没有问题,但是系统类访问应用类就会出现问题。比如在系统类中提供了一个接口,该接口需要在应用类中得以实现,该接口还绑定一个工厂方法,用于创建该接口的实例,而接口和工厂方法都在启动类加载器中。这时,就会出现该工厂方法无法创建由应用类加载器加载的应用实例的问题。

双亲委托模式的补充

  在Java平台中,把核心类(rt.jar)中提供外部服务,可由应用层自行实现的接口,通常可以称为Service Provider Interface,即SPI。
  为了解决启动类加载器无法访问应用类加载器加载的类的问题,Java中通过把一个ClassLoader置于一个线程实例中,这个ClassLoader叫做上下文加载器,使该ClassLoader成为一个相对共享的实例。默认情况下,上下文加载器就是应用类加载器,这样即使是在启动类加载器中的代码也可以通过这种方式访问应用类加载器的类,其示意图如下所示:


类加载机制ClassLoader简介_第2张图片

突破双亲模式

  双亲模式的类加载方式是虚拟机默认的行为,但并非必须这么做,通过重载ClassLoader可以修改该行为。事实上,不少应用软件和框架都修改了这种行为,比如Tomcat和OSGi框架,都有各自独特的类加载顺序。具体的过程就不嗷述了,感兴趣的可以取查阅资料。

热替换的实现

  热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。基本上大部分脚本语言都是天生支持热替换的,比图PHP,只要替换了PHP源文件,这种改动就会立即生效,而无需重启Web服务器。

  但对Java来说,热替换并非天生就支持,如果一个类已经加载到系统中,通过修改类文件,并无法让系统再来加载并重定义这个类。因此,在Java中实现这一功能的一个可行的方法就是灵活运用ClassLoader。

注意:由不同ClassLoader加载的同名类属于不同的类型,不能相互转换和兼容。即两个不同的ClassLoader加载同一个类,在虚拟机内部,会认为这2个类是完全不同的。

  根据这个特点,可以用来模拟热替换的实现,基本思路如下图所示:

类加载机制ClassLoader简介_第3张图片

你可能感兴趣的:(类加载机制ClassLoader简介)