news 2026/6/10 6:43:28

【JVM】类加载全过程双亲委派机制深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JVM】类加载全过程双亲委派机制深度解析

大家好,我是程序员二叉。


简介

本文梳理后端面试必考的类加载五大步骤、双亲委派机制原理、机制优缺点、打破方案、自定义类加载器完整实现逻辑,附带可运行代码示例。欢迎点赞关注收藏。


一、JVM类加载的五个步骤

类从磁盘.class文件到内存实例化对象,完整分为加载、验证、准备、解析、初始化五个阶段。

1. 加载 Loading

  1. 根据类的全限定名读取二进制字节流;
  2. 将字节流转换成方法区中的运行时数据结构;
  3. 在堆中生成代表这个类的Class对象,作为访问方法区类信息的入口。

2. 验证 Verification

校验字节码合法性、安全性,防止恶意代码破坏虚拟机,分为四层校验:

  • 文件格式验证:校验字节流是否符合Class文件规范;
  • 元数据验证:校验类的语义、继承关系等基础语法;
  • 字节码验证:校验方法体内指令逻辑安全;
  • 符号引用验证:校验引用的外部类/字段/方法是否可访问存在。

3. 准备 Preparation

  1. 给类中static静态变量分配内存空间,内存位于方法区;
  2. 给静态变量赋默认零值,而非代码中赋值;

示例:static int num = 100,准备阶段num=0,初始化阶段才赋值100
常量static final编译期直接赋值,准备阶段就赋予确定值。

4. 解析 Resolution

把常量池里的符号引用替换成内存中真实的直接引用
解析对象包含:类/接口、字段、普通方法、接口方法、方法句柄、调用点限定符等。

5. 初始化 Initialization

类加载最后一步,真正执行Java代码逻辑:

  1. 自动生成并执行类构造器<clinit>()方法;
  2. 按代码顺序给静态变量赋予代码定义的值;
  3. 顺序执行静态代码块;
    只有主动引用类时才会触发初始化,被动引用不会执行初始化。

二、什么是双亲委派机制

1. 三层原生类加载器层级

  1. 启动类加载器 Bootstrap ClassLoader:C++实现,加载JAVA_HOME/lib核心rt.jar等基础类;
  2. 扩展类加载器 Extension ClassLoader:Java实现,加载JAVA_HOME/lib/ext扩展包;
  3. 应用程序类加载器 App ClassLoader:系统默认加载器,加载项目classpath下自定义代码、第三方依赖包。

2. 委派执行流程

当一个类加载器收到加载请求:

  1. 自身先不尝试加载,向上委托父加载器处理;
  2. 层层向上传递,直到最顶层启动类加载器;
  3. 顶层加载器无法加载时,再逐级向下由子加载器尝试加载;
  4. 全部加载失败抛出ClassNotFoundException

3. 双亲委派核心优势

  1. 安全防护:防止恶意篡改Java核心类(比如自定义java.lang.String无法覆盖原生类);
  2. 全局唯一性:保证同一个全限定名的类在JVM中只存在一份Class实例;
  3. 避免重复加载:父加载器加载成功后,子类直接复用,减少IO加载开销。

三、双亲委派机制的缺点

  1. 上层加载器无法访问下层加载器的类
    启动类加载器、扩展加载器不能识别应用加载器加载的业务类,上下单向隔离;
  2. SPI服务扩展场景适配困难
    如JDBC、日志框架SPI,核心接口由启动类加载器加载,但实现类在项目classpath,默认委派模式拿不到实现类;
  3. 模块化、热部署、插件化场景受限
    OSGi、Tomcat多web应用隔离、代码热更新等场景,需要独立隔离类加载环境,原生委派无法实现;
  4. 无法实现类隔离
    多个模块依赖不同版本Jar包时,双亲委派只会加载第一个找到的Jar,版本冲突无法隔离。

四、如何打破双亲委派机制

1. 底层原理

ClassLoader加载入口是loadClass(String name),原生方法内置双亲委派逻辑;重写loadClass()方法,删除向上委托逻辑即可打破。

2. 三代打破方案

  1. 第一代:重写loadClass()
    完全重写加载逻辑,跳过父加载器委托,早期Tomcat、OSGi使用;
  2. 第二代:线程上下文类加载器 ContextClassLoader
    SPI标准解决方案,核心接口由启动加载器加载,通过线程上下文切换成应用加载器去加载实现类;
  3. 第三代:模块化自定义层级
    自定义平行类加载器,不遵循父层级,用于插件、多版本Jar隔离。

典型打破场景

  • JDBC驱动加载、Dubbo SPI、Spring SPI;
  • Tomcat多个Web工程独立类隔离;
  • OSGi模块化框架、开发工具热部署;
  • 中间件插件化架构。

五、如何实现自定义类加载器

规范实现规则

  1. 继承ClassLoader父类;
  2. 遵循双亲委派:只重写findClass(),不要改动loadClass();
  3. 在findClass中读取.class字节码,调用defineClass()转换成Class对象;
  4. 若要打破委派:重写loadClass(),屏蔽parent委托逻辑。

示例1:标准遵循双亲委派的自定义加载器

importjava.io.File;importjava.io.FileInputStream;publicclassCustomClassLoaderextendsClassLoader{// 自定义class文件存放路径privatefinalStringclassDir;publicCustomClassLoader(StringclassDir){this.classDir=classDir;}// 只重写findClass,保留原生双亲委派逻辑@OverrideprotectedClass<?>findClass(StringclassName)throwsClassNotFoundException{try{// 1. 读取class文件字节数组byte[]classBytes=readClassFile(className);if(classBytes==null){thrownewClassNotFoundException();}// 2. 字节码转为Class对象returndefineClass(className,classBytes,0,classBytes.length);}catch(Exceptione){thrownewClassNotFoundException(className,e);}}// 读取磁盘.class文件privatebyte[]readClassFile(StringclassName)throwsException{Stringpath=classDir+File.separator+className.replace(".",File.separator)+".class";try(FileInputStreamfis=newFileInputStream(path)){returnfis.readAllBytes();}}// 测试调用publicstaticvoidmain(String[]args)throwsException{CustomClassLoaderloader=newCustomClassLoader("D:/classpath");Class<?>testCls=loader.loadClass("com.demo.Test");Objectobj=testCls.newInstance();System.out.println(obj);}}

示例 2:打破双亲委派(重写 loadClass)

publicclassBreakDelegateClassLoaderextendsClassLoader{privatefinalStringclassDir;publicBreakDelegateClassLoader(StringclassDir){this.classDir=classDir;}// 重写加载入口,跳过父加载器委托@OverridepublicClass<?>loadClass(Stringname)throwsClassNotFoundException{// 1. 先查询缓存是否已加载Class<?>cacheClass=findLoadedClass(name);if(cacheClass!=null){returncacheClass;}// 不向上委托父加载器,直接自己加载try{byte[]bytes=readBytes(name);returndefineClass(name,bytes,0,bytes.length);}catch(Exceptione){// 自己加载失败,再交给父类兜底returnsuper.loadClass(name);}}privatebyte[]readBytes(StringclassName)throwsException{// 读取class文件逻辑省略,同上示例returnnewbyte[0];}}

总结(面试速记版)

  1. 类加载五步:加载 → 验证 → 准备 → 解析 → 初始化;
  2. 双亲委派:向上委托父加载器,父失败再子类加载;
  3. 优点:安全防篡改、类唯一、无重复加载;缺点:上下隔离、SPI 不兼容、插件化受限;
  4. 打破核心:重写loadClass(),主流方案上下文类加载器;
  5. 标准自定义加载器:继承ClassLoader,重写findClass();打破委派重写loadClass()
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 6:42:21

Go学习第3天:变量+常量+运算符

Go 语言变量、常量、运算符&#xff09;一、Go 变量1.1 变量命名规则说明合法/非法示例1.2 变量声明四大方式方式1&#xff1a;标准声明&#xff08;var 变量名 类型&#xff09;语法说明零值规则&#xff08;重点&#xff09;示例运行结果踩坑方式2&#xff1a;声明并初始化…

作者头像 李华
网站建设 2026/6/10 6:38:42

IDEA新手必看:保姆级教程教你从Gitee拉取团队项目(附常见错误解决)

IDEA新手必看&#xff1a;从Gitee拉取团队项目的完整指南与深度解析刚加入开发团队时&#xff0c;第一次接触版本控制系统总是让人既兴奋又紧张。作为团队协作的核心工具&#xff0c;Git和Gitee的正确使用直接关系到开发效率。本文将带你从零开始&#xff0c;不仅学会如何在IDE…

作者头像 李华
网站建设 2026/6/10 6:30:27

LPC540xx时序与电气特性深度解析:从数据手册到稳定硬件设计

1. 项目概述与核心价值在嵌入式硬件开发中&#xff0c;尤其是使用像NXP LPC540xx/LPC54S0xx这类基于ARM Cortex-M4内核的高性能微控制器时&#xff0c;很多工程师会陷入一个误区&#xff1a;认为只要程序逻辑正确&#xff0c;外设就能正常工作。然而&#xff0c;在实际项目中&a…

作者头像 李华