文章目录

  • Java类加载器原理和实践操作
  • 1、Java程序的启动并运行的过程
  • 2、类加载器加载过程
  • 1、Java8中内置的类加载器
  • 1、三种类加载器
  • 2、三种加载器之间的关系
  • 3、类加载的双亲委派机制
  • 1、双亲委派模型
  • 2、ClassLoader
  • 3、类加载器的特性


Java类加载器原理和实践操作

注:本文章中提到的“类”,是“非数组类 ”和“接口”的统称。

1、Java程序的启动并运行的过程

Java类加载是懒加载吗 java类加载原理_java

Java中的类加载都是在运行时动态完成的,这种动态加载的特性就是java语言灵活性的根源。同时在Java中,所有的类加载都是通过类加载器(ClassLoader)来完成。

2、类加载器加载过程

  1. 首先使用java代码或者jvm触发一个加载动作。
  2. 将类的全限定名传给类加载器。
  3. 类加载器通过类名获取到字节码的二进制流
  4. 根据字节码二进制流创建加载对应的Class对象

流程图图解:

Java类加载是懒加载吗 java类加载原理_java_02

1、Java8中内置的类加载器

运行示例及运行结果如图所示:

Java类加载是懒加载吗 java类加载原理_java_03

Java类加载是懒加载吗 java类加载原理_jvm_04

  • 由上我们可以发现Java8中内置的三个ClassLoader,分别是APPClassLoader、ExtClassLoader和BootstrapClassLoader。他们就是最主要的三种类加载器。

1、三种类加载器

  1. APPClassLoader,应用类加载器,默认的系统类加载器(System ClassLoader)

Java类加载是懒加载吗 java类加载原理_java_05

  • 应用类加载器用来加载当前Java应用classpath中的类
  1. ExtClassLoader,拓展类加载器

Java类加载是懒加载吗 java类加载原理_jvm_06

  • 拓展类加载器负责加载拓展目录中的类
  1. BootStrap ClassLoader,启动类加载器

Java类加载是懒加载吗 java类加载原理_加载_07

2、三种加载器之间的关系

三种类加载器之间的关系如下图所示:

Java类加载是懒加载吗 java类加载原理_加载_08

有两点需要注意:

  1. 从JVM的角度来看,只有两种类加载器。一种是BootStrap ClassLoader,他通常是JVM中的一部分,使用C/C++语言原生实现。另一种是用户定义的ClassLoader,包括了JDK中内置的APPClassLoader和ExtClassLoader以及用户自行实现的ClassLoader,其中用户定义的ClassLoader都是使用Java语言来实现的,并且要求他们都继承抽象类java.lang.ClassLoader
  2. BootStrap ClassLoader、APPClassLoader和ExtClassLoader并非是继承的关系,而是组合的关系。APPClassLoader显式拥有一个parent加载器ExtClassLoader,而ExtClassLoader加载器则隐式指向BootStrap ClassLoader。

3、类加载的双亲委派机制

双亲委派模型,英文名是“Parents Delegation Model”。

  • 翻译的特别差,容易产生一些误解:
  • 误解1:“双亲”,会被误解为存在两个“父/母”类加载器(这是错误的)
  • 1的正确理解:除了BootStrap ClassLoader,其他的类加载器有且只有一个parent
  • 误解2:“parent”,容易被误解为继承关系中的父类(这是错误的)
  • 2的正确理解:并非继承关系,而是组合关系

1、双亲委派模型

  • 每个Class都有对应的ClassLoader
  • 每个ClassLoader都有一个“父”类加载器(parent ClassLoader)。BootStrap ClassLoader除外,他是最顶层的类加载器。
  • 对于一个类加载的请求,总是优先委托给“父”类加载器来尝试加载
  • 对于用户自定义的类加载器,默认的“父”类加载器是AppClassLoader

类加载委派流程如下图所示:

Java类加载是懒加载吗 java类加载原理_jvm_09

所以当系统加载String类时,即使你自己定义了一个同名的String类,并且将其放置到classpath中,但是最终还是会首先委派给处于最顶层的启动类加载器中加载,即最终会加载rt.jar中的String类,而不是classpath中自行定义的String类。这样可以保证java运行的稳定性。

加载String类流程如图所示:

Java类加载是懒加载吗 java类加载原理_Java类加载是懒加载吗_10

双亲委派机制的实现代码非常简单,可以参考java.lang.ClassLoader中的方法LoadClass,核心代码十行左右。

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    //1 首先检查类是否被加载
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
             //2 没有则调用父类加载器的loadClass()方法;
                c = parent.loadClass(name, false);
            } else {
            //3 若父类加载器为空,则默认使用启动类加载器作为父加载器;
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
           //4 若父类加载失败,抛出ClassNotFoundException 异常后
            c = findClass(name);
        }
    }
    if (resolve) {
        //5 再调用自己的findClass() 方法。
        resolveClass(c);
    }
    return c;
}

2、ClassLoader

关键类ClassLoader中的几个关键方法:

  • LoadClass(…)

Java类加载是懒加载吗 java类加载原理_加载_11

此方法根据类的权限定名来加载并创建一个类对象的入口,如果遵循“双亲委派机制”,那么ClassLoader的子类尽量不要覆盖此方法。不过如果 需要打破双亲委派模型,那么ClassLoader的子类需要覆盖此方法,换成自行定义的逻辑。

  • defineClass(…)

Java类加载是懒加载吗 java类加载原理_类加载器_12

此方法将字节码的字节流转换成为一个Class对象,这是一个final方法,意味着子类无法覆盖它,最终是通过一个native原生方法,将字节流转换成Class对象。

  • findClass(…)

Java类加载是懒加载吗 java类加载原理_类加载器_13

此方法根据类的全限定名,获取字节码二进制流,并创建对应的Class对象,如果遵循“双亲委派模型”,那么我们通常不会覆盖LoadClass(…)方法,而是覆盖findClass(…)方法。

此方法的实现逻辑:a. 首先根据参数name,从指定来源获取字节码的二进制流。b. 然后调用defineClass(…)方法,创建一个class对象。

  • findBootstrapClassOrNull(…)

Java类加载是懒加载吗 java类加载原理_加载_14

此方法根据类的全限定名,委派BootStrap ClassLoader进行类的加载,注意这个方法是private的,这意味着:如果要将某个类加载的请求委派给BootStrap ClassLoader,那么必须间接调用类ClassLoader中的某个public和protected的方法。

  • getParent()

Java类加载是懒加载吗 java类加载原理_java_15

此方法获取当前ClassLoader的“父”类加载器。

  1. 注意parent字段是private final的,他只能在构造函数中初始化。
  2. parent!=null时,调用parent.loadClass(…)将加载请求委派给parent。
  3. parent==null时,调用findBootstrapClassOrNull(…)将请求委派给BootStrap ClassLoader。

3、类加载器的特性

  1. 用来确定类的“唯一性”
  • N:某个类的全限定名
  • L:加载定义这个类的类加载器ClassLoader
  • N,L:二元组<N,L>,可以用来确定类的唯一性

所以在查看两个类的相等,除了查看两个类的全限定名之外,还需要查看加载这两个类的类加载对象是否是同一个。

示例如下图所示:用两个类加载器加载同一个类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mBvQJnz-1662601557000)(https://gitee.com/acrazydragon/note_collection/raw/master/img/image-20220904221120569.png)]

示例运行结果如下图所示:

Java类加载是懒加载吗 java类加载原理_类加载器_16

  1. 传递性
  • 假设类C是由加载器L1定义加载的,那么类C中所依赖的其他类也会通过L1来进行加载。

类加载的实例流程图如下图所示:

Java类加载是懒加载吗 java类加载原理_类加载器_17

示例运行结果如下图所示:

[外链图片转存中…(img-QGVDvMsz-1662601557000)]

  1. 传递性
  • 假设类C是由加载器L1定义加载的,那么类C中所依赖的其他类也会通过L1来进行加载。

类加载的实例流程图如下图所示:

[外链图片转存中…(img-ehA47YrN-1662601557001)]

  1. 可见性