初识JVM-类加载器2

1前言

在上一小节内容中我们介绍了类加载过程的相关概念,在这一小节我们就接着上一小节的内容介绍一下类加载器的一些内容。
类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量的时间去调试 ClassNotFoundException和 NoClassDefFoundError等异常。本文将详细介绍 Java 的类加载器,帮助读者深刻理解 Java 语言中的这个重要概念。下面首先介绍一些相关的基本概念。

2类加载器概念

顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

3类加载器的分类

初识JVM-类加载器2_第1张图片
类加载器的分类.png
  1. Java虚拟机自带的加载器
    • 根类加载器(Bootstrap)
      根类加载器(Bootstrap):该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等,java.lang.String就是由跟类加载器加载的,根类加载器从系统属性sun.boot.class.path所指定的目录加载类库。跟类加载器的实现依赖于底层的操作系统,属于虚拟机实现的一部分,它并没有继承java.lang.ClassLoader类,该ClassLoader是由C++语言编写,所以我们无法得知其实现细节。
    • 扩展类加载器(Extension)
      扩展类加载器(Extension):它的父类加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录jre/lib/ext子目录中加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器,是由Java语言编写,是java.lang.ClassLoader类的子类。
    • 系统类加载器/应用类加载器(System)
      系统类加载器是从环境变量classpath或者系统属性java.class.path所指定的目录载类,它是用户自定义的类加载器的默认父类加载器。系统类加载也是由Java语言编写。是java.lang.ClassLoader的子类。

通过一个程序简单的验证一下

public static void main(String[] args) {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println("ClassLoaderName = " + classLoader);

        while (null != classLoader) {
            classLoader = classLoader.getParent();
            System.out.println("ClassLoaderName = " + classLoader);
        }
    }

输出结果

ClassLoaderName = sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoaderName = sun.misc.Launcher$ExtClassLoader@4554617c
ClassLoaderName = null

其中sun.misc.Launcher$AppClassLoader是系统类加载器,sun.misc.Launcher$ExtClassLoader是扩展类加载器,而根类加载器的值是null

  1. 用户自定义的类加载器
    除了虚拟机自带的加载器之外,用户还可以定制自己的类加载器(User-defined Class Loader)。Java提供了抽象类java.lang.ClassLoader,所有用户自定义类的加载器都应该继承ClassLoader类。

java除了给我提供JVM本身的类加载器外,还给我们提供了用户可自定义的类加载器。提高了加载器的灵活性。

4类加载的父委托机制

在父委托机制中,各个加载器按照父子关系形成了一个树形的机构,除了根类加载器之外,其余的类加载器都有且只有一个父加载器。

初识JVM-类加载器2_第2张图片
加载Sample类.png

Class sampleClass = loader2.loadClass("com.test.Sample")
loder2首先从自己的命名空间中查找Sample类是否已经被加载。如果已经加载就直接返回代表Sample类的Class对象的引用。
如果Sample类没有被加载,laoder2首先请求loader1代为加载,loader1再请系统类加载器代为加载,系统类加载器再请求扩展类加载器代为加载,扩展类记载其再请求根类加载器代为加载。若根类加载器和扩展类加载器则系统类加载器尝试加载,若能够加载成功则将Sample类所对应的Class对象的引用返回给loader1,loader1再将引用返回给loader2,从而成功将Sample类加载进虚拟机。若系统类加载器不能加载Sample类,则loader1尝试加载Sample类,若loader1也不能成功加载,则loader2尝试加载。若所有的父类加载器及loader2本身都不能加载,则抛出ClassNotFoundException异常。
若有一个类加载器能成功加载Sample类,那么这个类的加载器被称为 定义类加载器,所有能成功返回Class对象引用的类加载器(包括定义类加载器),都被称作 初始类加载器。在本例中 系统类加载器是定义类加载器, 系统类加载器,loader1都是初始类加载器。
除此之外,加载器之间的父子类实际上指的是加载器对象之间的包装关系,而不是类之间的继承关系。一堆父类加载可能是同一个加载器类的两个实例,也可能不是。在子加载器对象中包装了一个父加载器,例如一下 loader1loader2都是 MyClassLoader的类的实例,并且 loader2包装了 loader1,因此 loader1loader2的父加载器。

ClassLoader loader1 = new MyClassLoader();
// 参数loader1作为loader2的父加载器。
ClassLoader loader2 = new MyClassLoader(loader1);

父亲委托机制可以提高软件系统的安全性。在这种机制之下,用户自定义的加载器不能够加载本应该由父加载器加载的可靠类,从而方式不可靠甚至恶意的代码代替父加载器加载可好的代码,例如,java.lang.Object类总是由根类加载器加载,其他任何用户自定义的类加载器都不可能加载含有恶意代码的java.lang.Object

4名称空间

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现全路径名称相同的两个类;在不同的命名空间中,有可能会出现全路径相同的两个类。

5运行时包

由同一个类加载器的属于相同的包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看报名是否相同,还要看定义的类加载器是否相同。只有属于同一运行时包的类才能互相包访问可见(即默认访问级别)的类和类车官员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自定义一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。

6类的卸载

当一个类被加载,连接初始化之后,它的生命周期就开始了。当代表这个类的Class对象不再被引用,即不可触及的时候,Class对象就会结束生命周期,这个类在方法区内的数据也会被卸载,从而技术这个类的生命周期,由此可见,一个类合适结束生命周期,取决于代表它的Class对象合适结束生命周期。
由Java虚拟机自带的类加载器加载的类,在虚拟机的生命周期里,始终不会被卸载。Java虚拟机中自带的类加载器包括根类加载器,扩展类加载器,和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则始终引用他们所加载的类的Class对象,因此这些Class对象始终是可触及的。但是用户自定义的类加载器所加载的类是可以被卸载的

你可能感兴趣的:(初识JVM-类加载器2)