类加载器详解1

回顾一下类加载过程

开始介绍类加载器和双亲委派模型之前,简单回顾一下类加载过程。

  • 类加载过程:加载->连接->初始化
  • 连接过程又可分为三步:验证->准备->解析

类加载器详解1_第1张图片

类加载过程

加载是类加载过程的第一步,主要完成下面 3 件事情:

  1. 通过全类名获取定义此类的二进制字节流
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

类加载器

类加载器介绍

类加载器从 JDK 1.0 就出现了,最初只是为了满足 Java Applet(已经被淘汰) 的需要。后来,慢慢成为 Java 程序中的一个重要组成部分,赋予了 Java 类可以被动态加载到 JVM 中并执行的能力。

类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

从上面的介绍可以看出:

  • 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
  • 每个 Java 类都有一个引用指向加载它的 ClassLoader
  • 数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。
  • class Class {
      ...
      private final ClassLoader classLoader;
      @CallerSensitive
      public ClassLoader getClassLoader() {
         //...
      }
      ...
    }

    简单来说,类加载器的主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。 字节码可以是 Java 源程序(.java文件)经过 javac 编译得来,也可以是通过工具动态生成或者通过网络下载得来。

    其实除了加载类之外,类加载器还可以加载 Java 应用所需的资源如文本、图像、配置文件、视频等等文件资源。本文只讨论其核心功能:加载类。

    类加载器加载规则

    JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。

    对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

    public abstract class ClassLoader {
      ...
      private final ClassLoader parent;
      // 由这个类加载器加载的类。
      private final Vector> classes = new Vector<>();
      // 由VM调用,用此类加载器记录每个已加载类。
      void addClass(Class c) {
            classes.addElement(c);
       }
      ...
    }

    类加载器总结

    JVM 中内置了三个重要的 ClassLoader

  • BootstrapClassLoader(启动类加载器):最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 rt.jarresources.jarcharsets.jar等 jar 包和类)以及被 -Xbootclasspath参数指定的路径下的所有类。
  • ExtensionClassLoader(扩展类加载器):主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
  • AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
  • 除了这三种类加载器之外,用户还可以加入自定义的类加载器来进行拓展,以满足自己的特殊需求。就比如说,我们可以对 Java 类的字节码( .class 文件)进行加密,加载时再利用自定义的类加载器对其解密。

    类加载器详解1_第2张图片

    类加载器层次关系图

    除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。

    每个 ClassLoader 可以通过getParent()获取其父 ClassLoader,如果获取到 ClassLoadernull的话,那么该类是通过 BootstrapClassLoader 加载的。

    public abstract class ClassLoader {
      ...
      // 父加载器
      private final ClassLoader parent;
      @CallerSensitive
      public final ClassLoader getParent() {
         //...
      }
      ...
    }

    为什么 获取到 ClassLoadernull就是 BootstrapClassLoader 加载的呢? 这是因为BootstrapClassLoader 由 C++ 实现,由于这个 C++ 实现的类加载器在 Java 中是没有与之对应的类的,所以拿到的结果是 null。

    下面我们来看一个获取 ClassLoader 的小案例:

    public class PrintClassLoaderTree {
    
        public static void main(String[] args) {
    
            ClassLoader classLoader = PrintClassLoaderTree.class.getClassLoader();
    
            StringBuilder split = new StringBuilder("|--");
            boolean needContinue = true;
            while (needContinue){
                System.out.println(split.toString() + classLoader);
                if(classLoader == null){
                    needContinue = false;
                }else{
                    classLoader = classLoader.getParent();
                    split.insert(0, "\t");
                }
            }
        }
    
    }

    输出结果(JDK 8 ):

    |--sun.misc.Launcher$AppClassLoader@18b4aac2
        |--sun.misc.Launcher$ExtClassLoader@53bd815b
            |--null

    从输出结果可以看出:

  • 我们编写的 Java 类 PrintClassLoaderTreeClassLoaderAppClassLoader
  • AppClassLoader的父 ClassLoaderExtClassLoader
  • ExtClassLoader的父ClassLoaderBootstrap ClassLoader,因此输出结果为 null。

你可能感兴趣的:(java,开发语言)