天天看点

类加载机制2——类加载器

前置知识

类加载器是通过类的全限定名,来获取类的二进制字节流的代码。

类加载机制:jvm把class数据加载到内存,并对数据进行验证、准备、解析、初始化,最终形成可以被虚拟机使用的java类。

类的默认加载器,通过双亲委派机制进行类的加载。

加载类的加载器和类本身一起确定类的唯一性,若同一个class文件,被不同的加载器加载,则是不同的类。

双亲委派机制

类加载机制2——类加载器

启动类加载器:主要负责加载java的核心类库,/lib目录下的rt.jar、resources.jar、charsets.jar和class等

扩展类加载器:主要负责加载java扩展包中的类库,/lib/ext 目录下的jar包和class文件

应用类加载器:主要负责加载当前应用的classpath下的所有类

自定义加载器:加载指定路径的class文件

  当一个加载器收到加载一个类的请求时,会先把该请求委派给自己的父类加载器执行加载,故所有的类加载都会被委派到启动类加载器中,若父类加载器加载失败,才会自己尝试加载。

  在classloader类中的loadclass方法中实现

  

类加载机制2——类加载器

  以上源代码的逻辑为:

先检查类是否已经加载过

若类还未加载,则检查父类加载器是否存在,若存在,则调用父类加载器的loadclass方法进行加载;若不存在,则用启动加载器加载。

若父类加载器加载失败,则尝试自己加载,调用自己的加载方法findclass

使类跟类加载器一样拥有更严格的层级关系。如object类,无论在哪里使用,最终都会被委派给启动类加载器加载,保证object类在程序的各种类加载器环境中,都是同一个类。

如果没有使用双亲委派模型,可以由各个类加载器自行加载的话,如果用户自定义了一个名为java.lang.object类,放在程序的classpath中,那系统中就会出现多个不同的object类,导致应用程序执行的混乱。

  通过构造器注入parent

类加载机制2——类加载器

  自定义一个类加载器,继承抽象类classloader,重写loadclass方法,使其不进行双亲委派。

loadclass:默认实现的双亲委派机制

findclass:加载类class字节码,是当前类加载器的加载方法,若想自定义的类加载器也遵守双亲委派机制,则只需要重写findclass方法

defineclass:将类的字节码转换成class对象

   1、第1次是jdk1.2之前,那时已经有了类加载器和classloader类,但是不是双亲委派机制的

   2、第2次是模型自身的缺陷导致的,双亲委派机制使得越基础的类越由上层的加载器进行加载,正常情况下,用户代码继承、调用基础类,双亲委派机制加载没有问题。但是若从基础类中调回用户代码,则在上层加载器中,是无法找到下层的应用代码的,此时就需要破坏双亲委派机制,java中由基础类调用spi接口的地方都会如此。

   spi接口:service provider interface,如jndi、jdbc等。其本质是面向接口编程,具体实现或扩展由第三方在应用程序中实现。

   在上层类加载器中无法加载到应用程序中实现的具体类,如何解决这个问题?

   通过线程上下文类加载器实现contextclassloader。由thread类的setcontextclassloader方法设置,若未设置,则会继承父线程的类加载器。在应用程序中,若没有设置过这个值,则默认为应用程序类加载器。

    

类加载机制2——类加载器

    因此,在调用spi接口时,可以通过getcontextclassloader获取线程上下文加载器,通过应用程序加载器去加载所需的spi服务类。这是一种父类加载器去请求子类加载器完成类加载的过程,破坏了双亲委派机制。如drivermanager中的实现

        

类加载机制2——类加载器

  3、第3次破坏双亲委派机制是为了追求程序的动态性,如代码的热替换,程序的热部署等,即代码替换或模块替换后,不需要重启即可生效。

    这种实现是基于自定义类加载器实现的,此时加载器不再是有上下层级的树状结构,而是一个网状结构,每一个模块都有一个自定义的加载器,每当要替换掉一个模块时,就会将模块和类加载器都一起替换掉,以实现代码的热替换。   

参考书籍:《深入理解java虚拟机》第3版,作者:周志明