JVM虚拟机详解类加载器的分类

Posted _否极泰来_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM虚拟机详解类加载器的分类相关的知识,希望对你有一定的参考价值。

JVM虚拟机详解(三)类加载器的分类

1. 类加载器概述

  1. JVM严格来讲支持两种类型的类加载器 。分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
  2. 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器
  3. 无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个,如下所示

这里的四者之间是包含关系,不是上层和下层,也不是子系统的继承关系。

  • 我们通过一个类,获取它不同的加载器

    package com.peppa.classloader;
    
    /**
     * @author peppa
     * @create 2022-02-10 11:25:46
     */
    public class ClassLoaderTest 
        public static void main(String[] args) 
            // 获取系统类加载器
            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
            System.out.println(systemClassLoader);
    
            // 获取其上层的:扩展类加载器
            ClassLoader extClassLoader = systemClassLoader.getParent();
            System.out.println(extClassLoader);
    
            // 试图获取 根加载器
            ClassLoader bootstrapClassLoader = extClassLoader.getParent();
            System.out.println(bootstrapClassLoader);
    
            // 获取自定义加载器
            ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
            System.out.println(classLoader);
    
            // 获取String类型的加载器
            ClassLoader classLoader1 = String.class.getClassLoader();
            System.out.println(classLoader1);
        
    
    
    // 执行结果:
    sun.misc.Launcher$AppClassLoader@18b4aac2
    sun.misc.Launcher$ExtClassLoader@1b6d3586
    null
    sun.misc.Launcher$AppClassLoader@18b4aac2
    null
    

    得到的结果,从结果可以看出 根加载器无法直接通过代码获取,同时目前用户代码所使用的加载器为系统类加载器。同时我们通过获取String类型的加载器,发现是null,那么说明String类型是通过根加载器进行加载的,也就是说Java的核心类库都是使用根加载器进行加载的。

    我们尝试获取引导类加载器,获取到的值为 null ,这并不代表引导类加载器不存在,因为引导类加载器右 C/C++ 语言,我们获取不到

    两次获取系统类加载器的值都相同:sun.misc.Launcher$AppClassLoader@18b4aac2 ,这说明系统类加载器是全局唯一的

2.虚拟机自带的加载器

  • 启动类加载器(引导类加载器,Bootstrap ClassLoader)

    • 这个类加载使用C/C++语言实现的,嵌套在JVM内部。
    • 它用来加载Java的核心库(JAVAHOME/jre/1ib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
    • 并不继承自ava.lang.ClassLoader,没有父加载器。
    • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
    • 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类
  • 扩展类加载器(Extension ClassLoader)

    • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
    • 派生于ClassLoader类
    • 父类加载器为启动类加载器
    • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/1ib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
  • 应用程序类加载器(系统类加载器,AppClassLoader)

    • javI语言编写,由sun.misc.LaunchersAppClassLoader实现
    • 派生于ClassLoader类
    • 父类加载器为扩展类加载器
    • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
    • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
    • 通过classLoader#getSystemclassLoader()方法可以获取到该类加载器
  • 用户自定义类加载器

    在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。 为什么要自定义类加载器?

    • 隔离加载类
    • 修改类加载的方式
    • 扩展加载源
    • 防止源码泄漏

    用户自定义类加载器实现步骤:

    • 开发人员可以通过继承抽象类ava.1ang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求
    • 在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写1oadClass()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖1oadclass()方法,而是建议把自定义的类加载逻辑写在findclass()方法中
    • 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URIClassLoader类,这样就可以避免自己去编写findclass()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

3.查看根加载器所能加载的目录

刚刚我们通过概念了解到了,根加载器只能够加载 java /lib目录下的class,我们通过下面代码验证一下

package com.peppa.classloader;

import java.net.URL;
import java.security.Provider;

/**
 * @author peppa
 * @create 2022-02-10 11:42:4
 */
public class ClassLoaderPathTest 
    public static void main(String[] args) 
        System.out.println("*********启动类加载器************");
        // 获取BootstrapClassLoader 能够加载的API的路径
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls) 
            System.out.println(url.toExternalForm());
        

        // 从上面路径中,随意选择一个类,来看看他的类加载器是什么:得到的是null,说明是  根加载器
        ClassLoader classLoader = Provider.class.getClassLoader();
        System.out.println("classLoader ==>"+classLoader);
    


// 得到的结果
*********启动类加载器************
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/resources.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/rt.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/sunrsasign.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/jsse.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/jce.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/charsets.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/lib/jfr.jar
file:/D:/software/Java/jdk1.8/jdk1.8.0_241/jre/classes
classLoader ==>null

4. 关于ClassLoader

  • ClassLoader 类介绍

    ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)

  • sun.misc.Launcher 它是一个java虚拟机的入口应用

  • 获取ClassLoader的途径
    • 获取当前ClassLoader:clazz.getClassLoader()
    • 获取当前线程上下文的ClassLoader:Thread.currentThread().getContextClassLoader()
    • 获取系统的ClassLoader:ClassLoader.getSystemClassLoader()
    • 获取调用者的ClassLoader:DriverManager.getCallerClassLoader()

5.双亲委派机制

Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
  4. 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常

  • 双亲委派机制举例

    当我们加载jdbc.jar 用于实现数据库连接的时候,首先我们需要知道的是 jdbc.jar是基于SPI接口进行实现的,所以在加载的时候,会进行双亲委派,最终从根加载器中加载 SPI核心类,然后在加载SPI接口类,接着在进行反向委派,通过线程上下文类加载器进行实现类 jdbc.jar的加载。

  • 沙箱安全机制

    自定义string类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\\lang\\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

  • 双亲委派机制的优势

    通过上面的例子,我们可以知道,双亲机制可以

    • 避免类的重复加载
    • 保护程序安全,防止核心API被随意篡改
      • 自定义类:java.lang.String
      • 自定义类:java.lang.ShkStart(报错:阻止创建 java.lang开头的类)
  • 其他

    • 如何判断两个class对象是否相同?

      在JVM中表示两个class对象是否为同一个类存在两个必要条件:

      1. 类的完整类名必须一致,包括包名
      2. 加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
      3. 换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的
    • 对类加载器的引用

      1. JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的
      2. 如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中

以上是关于JVM虚拟机详解类加载器的分类的主要内容,如果未能解决你的问题,请参考以下文章

Java虚拟机(JVM)-- 类加载器和双亲委派机制

JVM:类加载器的双亲委派模型

深入理解JVM(③)虚拟机的类加载器(双亲委派模型)

Java 虚拟机程序执行:02 虚拟机的类加载机制

JVM类加载器篇(下)

JVM体系结构详解