Code Ease Code Ease
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档

神秘的鱼仔

你会累是因为你在走上坡路
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档
服务器
  • Java核心基础

    • 基础篇

    • 集合类

    • JVM虚拟机

      • JVM的内存分代,这篇文章帮你理一理
      • 用几张图深度剖析Java运行时数据区
      • JVM垃圾回收机制是怎样的,何时触发YoungGC或FullGC操作,一文搞定
      • 关于垃圾收集器你了解多少?一文总结七大垃圾收集器
      • 深入浅出CMS垃圾收集器
      • 图解类加载器和双亲委派机制,一看就懂
        • (一)概述
        • (二)类加载的过程
        • (三)类加载器的分类
          • 3.1 启动类加载器
          • 3.2 拓展类加载器
          • 3.3 应用程序类加载器
        • (四)双亲委派模型
        • (五)破坏双亲委派
      • 关于JVM调优,我理了一些工具和思路出来
      • 两张图让你快速读懂JVM字节码指令
      • 从JVM角度思考--如何预估线上环境机器资源大小
    • Java并发

  • 框架的艺术

  • 分布式与微服务

  • 开发经验大全

  • 版本新特性

  • Java
  • Java核心基础
  • JVM虚拟机
CodeEase
2023-09-19
目录

图解类加载器和双亲委派机制,一看就懂

作者:鱼仔
博客首页: codeease.top (opens new window)
公众号:Java鱼仔

# (一)概述

我们都知道Java代码会被编译成class文件,在class文件中描述了该类的各种信息,class类最终需要被加载到虚拟机中才能运行和使用。

虚拟机把Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成虚拟机可以直接使用的Java类型,这就是虚拟机的类加载机制。

# (二)类加载的过程

一个类从被加载到卸载出内存,一共包含下面七个阶段:

加载、验证、准备、解析、初始化、使用、卸载

6-1.png

加载的来源有以下部分:

1、本地磁盘

2、网络下载的.class文件

3、war,jar下加载.class文件

4、从专门的数据库中读取.class文件(少见)

5、将java源文件动态编译成class文件,典型的就是动态代理,通过运行时生成class文件

加载的过程是通过类加载器实现的。有关类加载的其他过程我会在下一章中介绍。

# (三)类加载器的分类

类加载器分为系统级别和用户级别:

系统级别的类加载器有:

1、启动类加载器(底层使用C++实现)

2、扩展类加载器(底层使用java实现,是ClassLoader的子类)

3、应用程序类加载器(底层使用java实现,是ClassLoader的子类)

用户级别的类加载器我们统一称为自定义类加载器。

# 3.1 启动类加载器

首先我们来看看启动类加载器加载了哪些类,启动类加载器负责加载sun.boot.class.path:

public static void bootClassLoaderLoadingPath(){
    //获取启动列加载器加载的目录
    String bootStrapLoadingPath=System.getProperty("sun.boot.class.path");
    //把加载的目录转为集合
    List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
    for (String bootPath:bootLoadingPathList){
        System.out.println("启动类加载器加载的目录:"+bootPath);
    }
}
1
2
3
4
5
6
7
8
9

通过上面的代码我们可以获取到启动类加载器所加载的类:

6-2.png

# 3.2 拓展类加载器

扩展类加载器加载负责加载java.ext.dirs,我们同样写一段代码去加载它:

public static void extClassLoaderLoadingPath(){
    //获取启动列加载器加载的目录
    String bootStrapLoadingPath=System.getProperty("java.ext.dirs");
    //把加载的目录转为集合
    List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
    for (String bootPath:bootLoadingPathList){
        System.out.println("拓展类加载器加载的目录:"+bootPath);
    }
}
1
2
3
4
5
6
7
8
9

可以看到,除了加载了JDK目录下的ext外,还加载了Sun目录下的ext

6-3.png

# 3.3 应用程序类加载器

最后是应用类加载器,它负责加载java.class.path:

public static void appClassLoaderLoadingPath(){
    //获取启动列加载器加载的目录
    String bootStrapLoadingPath=System.getProperty("java.class.path");
    //把加载的目录转为集合
    List<String> bootLoadingPathList= Arrays.asList(bootStrapLoadingPath.split(";"));
    for (String bootPath:bootLoadingPathList){
        System.out.println("应用程序类加载器加载的目录:"+bootPath);
    }
}
1
2
3
4
5
6
7
8
9

它负责加载工程目录下classpath下的class以及jar包。

# (四)双亲委派模型

所谓双亲委派模型,就是指一个类接收到类加载请求后,会把这个请求依次传递给父类加载器(如果还有的话),如果顶层的父类加载器可以加载,就成功返回,如果无法加载,再依次给子加载器去加载。

6-4.png

我们先通过代码来看一下类加载器的层级结构:

public class ClassLoaderPath {
    public static void main(String[] args) {
        System.out.println(ClassLoaderPath.class.getClassLoader());
        System.out.println(ClassLoaderPath.class.getClassLoader().getParent());
        System.out.println(ClassLoaderPath.class.getClassLoader().getParent().getParent());
    }
}
1
2
3
4
5
6
7

编写一个类,依次输出这个类的类加载器,父类加载器,父类的父类加载器

6-5.png

可以看到首先是应用程序类加载器,它的父类是扩展类加载器,扩展类加载器的父类输出了一个null,这个null会去调用启动类加载器。如果你不信,我们看源码:ClassLoader类

6-6.png

接着从父类加载器往下调用findClass,如果可以加载,就直接返回class,如果不能加载,就依次向下。如果到了自定义加载器还是无法被加载,就会抛出ClassNotFound异常。

我画了一个流程图来展示双亲委派模型的全过程:

6-7.jpeg

双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载,也保证了 Java 的核心 API 不被篡改。

# (五)破坏双亲委派

双亲委派模型并不是绝对的,spi机制就可以打破双亲委派模型。

首先我们需要了解什么是spi,spi(Service Provider Interface)是一种服务发现机制,Java在核心库中定义了许多接口,并且针对这些接口给出调用逻辑,但是并未给出具体的实现。开发者要做的就是定制一个实现类,在 META-INF/services 中注册实现类信息,以供核心类库使用。最典型的就是JDBC。

Java提供了一个Driver接口用于驱动各个厂商的数据库连接,Driver类位于JAVA_HOME中jre/lib/rt.jar中,应该由Bootstrap类加载器进行加载。根据类加载机制,当被加载的类引用了另外一个类的时候,虚拟机就会使用加载该类的类加载器加载被引用的类,因此如果其他数据库厂商定制了Driver的实现类之后,按理说也得把这个实现类放到启动类加载器加载的目录下,这显然是很不合理的。

于是Java提供了spi机制,即使Driver由启动类加载器去加载,但是他可以让线程上下文加载器(Thread Context ClassLoader)去请求子类加载器去完成加载,默认是应用程序类加载器。但是这确实破坏了类加载机制。

上次更新: 2025/04/29, 17:22:06
深入浅出CMS垃圾收集器
关于JVM调优,我理了一些工具和思路出来

← 深入浅出CMS垃圾收集器 关于JVM调优,我理了一些工具和思路出来→

最近更新
01
AI大模型部署指南
02-18
02
半个月了,DeepSeek为什么还是服务不可用
02-13
03
Python3.9及3.10安装文档
01-23
更多文章>
Theme by Vdoing | Copyright © 2023-2025 备案图标 浙公网安备33021202002405 | 浙ICP备2023040452号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式