package com.ai;

// HelloWorld.java
public class HelloWord {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
        System.out.println(System.getProperty("os.name"));
        System.out.println(System.currentTimeMillis());
    }
}
javac HelloWorld.java   # 编译生成字节码
java HelloWorld         # 执行程序

javap -c HelloWord.class 得到

package com.ai;

// HelloWorld.java
public class HelloWord {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
        System.out.println(System.getProperty("os.name"));
        System.out.println(System.currentTimeMillis());
    }
}
OS JVM Javac 开发者 OS JVM Javac 开发者 编写HelloWorld.java 生成HelloWorld.class(字节码) 申请内存资源 类加载/验证/准备/解析/初始化 执行引擎解释/编译字节码 调用系统输出函数 显示"Hello, World!"

3️⃣ 核心执行阶段详解
阶段一:类加载过程

加载:查找并加载HelloWorld.class
双亲委派模型:Bootstrap→Extension→Application
验证:检查字节码合法性
文件格式/元数据/字节码验证
准备:为静态变量分配内存
static变量赋初始值
解析:符号引用转直接引用
初始化:执行()方法

阶段二:内存分配

60% 15% 15% 5% 5% HelloWorld运行时内存分布 方法区(类信息) 堆(对象实例) 虚拟机栈(栈帧) 本地方法栈 程序计数器

你理解的“加载A→发现要加载B→加载B→发现要加载C”完全没错!这就是JVM类动态加载的核心——按需加载+依赖链式触发,我用“人话+例子+可视化流程”把这个过程讲透,让你彻底理解“动态加载”和“链式反应”的本质。

你现在拿到的是 com.ai.HelloWord 类的字节码反编译结果(javap -c 输出),我会逐行拆解每一段字节码的含义,从类结构、构造方法、main方法三个维度讲透,包括每个指令的作用、对应Java源码的逻辑,新手也能看懂。

先明确前提

这份字节码对应的Java源码应该是:

package com.ai; // 包名
public class HelloWord { // 注意:你写的是Word(少了l),不是World
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

字节码是Java源码编译后的“机器语言”,JVM就是靠执行这些指令来运行程序的。


第一部分:文件头(编译信息)

Compiled from "HelloWord.java"
  • 作用:标注这个字节码文件是从 HelloWord.java 源文件编译而来;
  • 注意:这里文件名是 HelloWord.java(Word),和类名一致,所以编译/反编译都没报错(但拼写是笔误)。

第二部分:类声明

public class com.ai.HelloWord {
  • 作用:声明这是一个公共类,全限定类名是 com.ai.HelloWord(包名+类名);
  • 字节码里的类名必须带完整包名,JVM靠全限定名区分不同包下的同名类(比如 com.ai.HelloWordcom.test.HelloWord 是两个不同类)。

第三部分:默认构造方法(编译器自动生成)

  public com.ai.HelloWord();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

先解释:为什么会有这个方法?

你的Java源码里没写构造方法,但Java编译器会为没有显式构造方法的类自动生成一个无参默认构造方法,作用是创建类的实例(比如 new HelloWord())。

逐行解析指令

指令行 字节码指令 对应操作(人话解释) 底层逻辑
0 aload_0 把当前对象(this)的引用加载到操作数栈 aload_0 是“加载局部变量表第0位到栈”——构造方法的第0个局部变量永远是 this(当前对象),这行就是把 this 压入栈,为调用父类构造方法做准备
1 invokespecial #1
// Method java/lang/Object.“”😦)V
调用父类(Object)的无参构造方法 1. invokespecial:专门调用“实例初始化方法()”或父类方法,不能被重写,保证父类构造方法优先执行;
2. #1:常量池索引,指向 java/lang/Object."<init>":()V(Object的无参构造方法);
3. 所有Java类都继承Object,所以构造方法第一行默认调用 super()(Object的构造方法)
4 return 构造方法执行完毕,返回 无返回值(构造方法没有返回类型),只是结束方法执行

对应Java源码(编译器自动加的)

public HelloWord() {
    super(); // 调用Object的构造方法,编译器自动加
}

第四部分:main方法(程序入口)

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Hello, World!
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

先明确:main方法是程序入口的原因

public static void main(String[] args) 是JVM规定的程序入口格式:

  • public:JVM能从外部调用这个方法;
  • static:不用创建类实例就能调用(JVM直接执行 HelloWord.main());
  • void:无返回值;
  • String[] args:接收命令行参数(比如 java com.ai.HelloWord 参数1)。

逐行解析指令

指令行 字节码指令 对应操作(人话解释) 底层逻辑
0 getstatic #2
// Field java/lang/System.out:Ljava/io/PrintStream;
获取System类的静态变量out(也就是System.out) 1. getstatic:获取静态变量的值(静态变量存在方法区,不属于对象);
2. #2:常量池索引,指向 java/lang/System.out(PrintStream类型的静态对象);
3. 这行执行后,操作数栈里会压入 System.out 的引用
3 ldc #3
// String Hello, World!
把字符串常量“Hello, World!”加载到操作数栈 1. ldc:从常量池加载常量(字符串、int/float等)到栈;
2. #3:常量池索引,指向字符串常量“Hello, World!”(Java中字符串常量存在常量池,复用且不可变);
3. 此时栈里有两个元素:先压入的 System.out,后压入的“Hello, World!”
5 invokevirtual #4
// Method java/io/PrintStream.println:(Ljava/lang/String;)V
调用System.out的println方法,传入字符串参数 1. invokevirtual:调用对象的实例方法(可重写),JVM会根据对象的实际类型找方法(多态的核心);
2. #4:常量池索引,指向 PrintStream.println(String) 方法;
3. 执行过程:JVM从栈里弹出参数(“Hello, World!”)和对象(System.out),调用 println 方法打印字符串
8 return main方法执行完毕,返回 main方法是void类型,结束后程序退出

对应Java源码的执行逻辑

public static void main(String[] args) {
    // 指令0:获取System.out对象
    PrintStream out = System.out;
    // 指令3:加载字符串常量
    String str = "Hello, World!";
    // 指令5:调用out.println(str)
    out.println(str);
    // 指令8:方法返回
    return;
}

补充:常量池索引的含义(#1/#2/#3/#4)

javap -c 里的 #1/#2/#3/#4常量池的索引,常量池是.class文件里存储常量(字符串、类名、方法名、字段名)的区域,JVM通过索引快速查找常量:

  • #1java/lang/Object."<init>":()V(Object的无参构造方法);
  • #2java/lang/System.out:Ljava/io/PrintStream;(System.out字段);
  • #3String Hello, World!(字符串常量);
  • #4java/io/PrintStream.println:(Ljava/lang/String;)V(println方法)。

如果想查看完整常量池,执行 javap -v com.ai.HelloWord(加 -v 显示详细信息),就能看到所有索引对应的常量。


总结(核心要点回顾)

  1. 默认构造方法:源码没写构造方法时,编译器自动生成,核心是调用父类(Object)的构造方法;
  2. main方法字节码
    • getstatic 获取静态变量 System.out
    • ldc 加载字符串常量到栈;
    • invokevirtual 调用 println 方法打印字符串;
  3. 字节码核心逻辑:JVM通过“操作数栈+局部变量表+常量池”执行指令,所有Java代码最终都会转换成这种极简的字节码指令。

如果想验证,你可以给源码加一个显式构造方法,再执行 javap -c,就能看到构造方法的字节码会跟着变化~

一、先给核心定义:什么是类的“动态加载”?

“动态加载”的核心是:类不是提前一次性加载,而是在程序运行到“需要用这个类”的那一刻,才触发加载流程;且加载过程中会自动触发“依赖类”的加载,形成链式反应——就像你拆快递:拆第一个包裹(加载A),发现里面有个配件需要拆第二个包裹(加载B),拆B又发现需要拆第三个包裹(加载C),直到所有依赖都拆完,才能用A。

二、用你的例子(System.out.println)拆解“链式加载”全过程

我们以System.out.println("Hello")为例,可视化整个链式加载流程,你能清晰看到“加载A→B→C→D”的链式反应:

未加载(JVM启动时已加载,这里假设首次用)

未加载

未加载

未加载

已加载

已加载

已加载

已加载

执行System.out.println → 需要用System类

System类是否已加载?

加载System类

解析System类:发现out字段是PrintStream类型

PrintStream类是否已加载?

加载PrintStream类

解析PrintStream类:发现依赖FileOutputStream类

FileOutputStream类是否已加载?

加载FileOutputStream类

解析FileOutputStream类:发现依赖FileDescriptor类

FileDescriptor类是否已加载?

加载FileDescriptor类

所有依赖加载完成,初始化System.out

执行println方法

分步拆解(新手能看懂的细节):
  1. 触发点:程序执行到System.out.println,首先需要访问System类的静态字段out

    • 此时JVM先检查:System类是否已经加载?(JVM启动时其实已经加载,但如果是自定义类,这里就是首次加载)。
  2. 第一步加载:System类

    • 加载System类后,JVM会“解析”这个类的字节码——发现out字段的类型是PrintStreampublic static final PrintStream out;);
    • JVM立刻检查:PrintStream类是否已加载?(此时还没加载,触发下一步)。
  3. 第二步加载:PrintStream类

    • 加载PrintStream类后,解析其字节码:发现PrintStream的构造方法依赖FileOutputStreampublic PrintStream(FileOutputStream out));
    • JVM检查:FileOutputStream类是否已加载?(未加载,触发第三步)。
  4. 第三步加载:FileOutputStream类

    • 加载FileOutputStream类后,解析其字节码:发现它依赖FileDescriptor(构造方法需要FileDescriptor对象,比如FileDescriptor.out);
    • JVM检查:FileDescriptor类是否已加载?(未加载,触发第四步)。
  5. 第四步加载:FileDescriptor类

    • FileDescriptor是基础类,无其他核心依赖,加载完成后,链式加载终止。
  6. 收尾:初始化所有类

    • 所有依赖类加载完成后,JVM反向初始化:先初始化FileDescriptor → 再初始化FileOutputStream → 再初始化PrintStream → 最后给System.out赋值;
    • 至此,System.out才真正可用,执行println方法。

三、“链式加载”的核心规则(新手必记)

  1. 触发条件:只有“真正用到类”时才触发加载,比如:

    • 访问类的静态字段(如System.out);
    • 创建类的实例(如new ArrayList());
    • 调用类的静态方法(如Math.abs());
    • 反射调用类(如Class.forName(“java.util.ArrayList”))。
      仅仅是“声明变量类型”不会触发加载(比如PrintStream ps;,只声明不使用,PrintStream不会加载)。
  2. 加载顺序:“先加载依赖,再加载主类”——就像盖房子,先打地基(依赖类),再盖主体(主类)。

  3. 缓存机制:每个类只加载一次,链式加载中如果某个类已经加载过(比如之前用过FileDescriptor),会直接用缓存,不会重复加载。

  4. 终止条件:当加载到“无其他未加载依赖”的类时,链式反应终止(比如FileDescriptor类无核心依赖)。

四、举个更直观的“自定义类”例子(非系统类)

假设你写了三个类,存在依赖关系:

// A类依赖B类
class A {
    B b = new B(); // 创建B实例,触发B加载
}

// B类依赖C类
class B {
    C c = new C(); // 创建C实例,触发C加载
}

// C类无依赖
class C {}

// 主类
public class Test {
    public static void main(String[] args) {
        A a = new A(); // 首次使用A,触发链式加载
    }
}

sequenceDiagram
participant Developer
participant Javac
participant JVM
participant OS
Developer->>Javac: 编写Java代码并调用编译
Javac->>Developer: 返回编译结果
Developer->>JVM: 运行编译后的字节码
JVM->>OS: 请求系统资源
OS–>>JVM: 提供系统资源
JVM–>>Developer: 返回运行结果

4️⃣ 关键组件协作
JVM组件 在HelloWorld中的作用
类加载器 加载HelloWorld.class到方法区
执行引擎 解释/编译字节码为机器指令
本地方法栈 执行println()的native方法调用
垃圾收集器 回收临时String对象
执行new A()时的链式加载流程:

  1. 用到A → 加载A → 发现A依赖B → 加载B;
  2. 加载B → 发现B依赖C → 加载C;
  3. 加载C → 无依赖 → 链式终止;
  4. 初始化C → 初始化B → 初始化A → 创建A实例。

总结(核心要点回顾)

  1. 动态加载:类在“运行时用到的那一刻”才加载,而非启动时一次性加载;
  2. 链式反应:加载类A时,发现A依赖B → 加载B,B依赖C → 加载C,直到所有依赖加载完成;
  3. 核心规则:按需触发、先加载依赖、只加载一次、无依赖则终止;
  4. 本质目的:节省内存、加快程序启动速度,是JVM高效运行的核心设计。

如果想亲眼看到这个链式加载过程,你可以执行java -verbose:class Test(Test是上面的主类),控制台会打印每一个被加载的类名,能清晰看到“A→B→C”的加载顺序~

你现在的疑问特别关键——字节码不是直接和Windows交互,而是JVM充当了“中间翻译官”,字节码也不用死记,核心是理解JVM的执行模型+和操作系统的交互逻辑,我用“人话+类比+核心原理”的方式讲透,再解答算法/数据结构的关联:

一、先搞懂:字节码(HelloWord)怎么和Windows OS交互?(核心逻辑)

你可以把整个过程想象成“三层分工”,字节码只和JVM对话,JVM再和Windows打交道,字节码完全不直接碰操作系统

javac编译

java命令启动JVM

调用Windows系统调用

操作硬件

Java源码 HelloWord.java

字节码 HelloWord.class

JVM(Java虚拟机)

Windows OS(操作系统)

显示器(打印Hello World)

分步拆解(以System.out.println为例):
1. 第一步:你执行java com.ai.HelloWord → 启动JVM(关键!)
  • Windows上的java.exe是JVM的Windows版本(JVM针对不同系统有不同实现:Windows版、Linux版、Mac版);
  • 执行java命令时,Windows会创建一个JVM进程(和你打开微信、浏览器一样,是Windows的一个进程);
  • JVM进程加载HelloWord.class字节码,准备执行。
2. 第二步:JVM执行字节码指令(getstatic/ldc/invokevirtual
  • JVM内部有自己的“执行引擎”(相当于JVM的CPU),负责解析字节码指令;
  • 执行到invokevirtual #4(调用println)时,JVM知道要“打印字符串”,但它自己不会直接操作显示器——它会调用JDK的底层代码。
3. 第三步:JDK底层代码调用Windows系统API(和OS交互的核心)
  • System.out.println的底层不是Java代码,而是JDK的C/C++代码(Windows版JDK用C++写的);
  • 比如打印字符串时,JDK的C++代码会调用Windows的系统调用/API
    • 比如WriteConsole(Windows控制台输出API)、CreateFile(打开控制台设备)等;
  • 这些Windows API是操作系统提供的“接口”,任何程序(Java/Python/C++)想和Windows交互,都必须通过这些API。
4. 第四步:Windows OS操作硬件(显示器打印)
  • Windows收到JDK的API调用后,会让内核执行“控制台输出”操作:
    • 内核告诉显卡驱动,把“Hello, World!”画到控制台窗口;
    • 显卡驱动操作显卡硬件,最终显示器显示文字。
关键总结(新手必记):
  • 字节码 → JVM → JDK本地方法(C/C++) → Windows API → Windows OS → 硬件;
  • 字节码是“跨平台的中间语言”,JVM是“跨平台的翻译官”——这就是Java“一次编写,到处运行”的核心。

二、怎么“读懂”字节码?(不用死记,抓核心规律)

你不用背每一条字节码指令,只要掌握“JVM执行模型”和“指令的核心分类”,就能看懂任何字节码:

1. 先懂JVM执行字节码的“基础环境”(两个核心结构)

JVM执行方法(比如main方法)时,会为每个方法创建一个栈帧(Stack Frame),栈帧里有两个关键区域:

  • 局部变量表:存方法的参数、局部变量(比如main方法的args,构造方法的this);
  • 操作数栈:存临时数据,字节码指令就是“往栈里压数据/从栈里弹数据/调用方法”。
2. 字节码指令的核心分类(记这5类就够了)
指令类型 作用 例子(你代码里的)
加载/存储指令 操作局部变量表和操作数栈 aload_0(加载this到栈)
常量指令 加载常量到操作数栈 ldc #3(加载字符串常量)
字段指令 操作类/对象的字段 getstatic #2(获取System.out)
方法调用指令 调用方法 invokespecial(调用构造方法)、invokevirtual(调用println)
返回指令 结束方法 return
3. 读懂字节码的“固定套路”(以main方法为例)

不管多复杂的字节码,都按这个套路看:

  1. 先看“准备数据”:哪些指令往操作数栈里压数据(比如getstatic压System.out,ldc压字符串);
  2. 再看“执行操作”:哪些指令调用方法/操作数据(比如invokevirtual调用println);
  3. 最后看“结束方法”:return指令。

比如你代码里的main方法:

0: getstatic #2 → 压System.out到栈
3: ldc #3 → 压"Hello, World!"到栈
5: invokevirtual #4 → 从栈弹参数和对象,调用println
8: return → 结束

就这么简单,不用纠结指令编号,只看“数据怎么进栈、怎么被使用”。

三、字节码/Java执行过程涉及哪些算法和数据结构?

核心的算法和数据结构都在JVM层面,字节码本身是“指令序列”,但执行它依赖这些关键结构:

1. 核心数据结构(必知)
数据结构 在哪里用 作用
栈(Stack) JVM栈、操作数栈、栈帧 1. JVM栈存方法的栈帧(方法调用=压栈,方法结束=出栈);
2. 操作数栈存字节码执行的临时数据(比如println的参数);
(你代码里的所有指令都围绕操作数栈)
常量池(常量数组) .class文件、JVM方法区 存字符串、类名、方法名、字段名(比如#1/#2/#3/#4就是常量池的索引)
哈希表(HashMap) JVM的方法区(类加载)、字符串池 1. 类加载时,JVM用哈希表缓存已加载的类(比如com.ai.HelloWord);
2. 字符串常量池用哈希表存储字符串,实现“字符串复用”(比如多次写"Hello World"只存一份)
链表/红黑树 JVM的垃圾回收(GC)、线程调度 1. GC时用链表管理对象;
2. JVM的线程调度用红黑树管理线程优先级
2. 核心算法(必知)
算法 在哪里用 作用
解释执行算法 JVM执行引擎 逐行解析字节码指令并执行(你代码的字节码就是这么执行的)
即时编译(JIT)算法 JVM执行引擎(热点代码优化) 把频繁执行的字节码编译成机器码(比如循环执行的代码),提升速度
垃圾回收(GC)算法 JVM的堆内存管理 回收不再使用的对象(比如System.out用完后,GC会判断是否回收)
方法查找算法 JVM调用方法时(invokevirtual) 比如多态时,JVM用“方法表”快速找到对象的实际方法(比如println)

总结(核心要点回顾)

  1. 和Windows交互的路径:字节码→JVM→JDK本地方法(C++)→Windows API→Windows OS→硬件,字节码不直接碰操作系统;
  2. 读懂字节码的关键:掌握“栈帧+操作数栈”模型,只看“数据入栈→执行操作→返回”的核心流程,不用死记指令;
  3. 涉及的算法/数据结构:核心是栈(操作数栈/JVM栈)、常量池、哈希表,算法是解释执行、GC、JIT编译。

如果还是想不通,我们可以聚焦“System.out.println打印到Windows控制台”这个场景,拆到最细的步骤(比如JVM怎么调用Windows的WriteConsole API),你想深入哪一步都可以~

你问的双亲委派机制是JVM类加载最核心的设计之一,本质是「类加载器的分工+优先级规则」,目的是保证Java类的安全性和唯一性。我用「公司审批流程」的类比讲透,再拆解规则、流程、作用,新手也能一眼看懂。

一、先搞懂核心类比:把类加载器比作“公司审批部门”

假设你是公司员工(用户代码),要申请一笔经费(加载一个类,比如java.lang.String),公司有三级审批部门(类加载器):

类加载器类型 类比公司部门 职责
启动类加载器(Bootstrap) 董事长(最高级别) 审批核心事项(加载rt.jar里的核心类:String、System、PrintStream)
扩展类加载器(Extension) 总监 审批扩展事项(加载jre/lib/ext下的扩展类)
应用类加载器(Application) 部门经理(最低级别) 审批日常事项(加载你自己写的类、第三方jar包)

双亲委派的核心逻辑:你要申请经费,先找部门经理(应用类加载器) → 经理不直接批,先上报给总监(扩展类加载器)→ 总监也不批,上报给董事长(启动类加载器)→ 只有董事长说“这事我不管”,才会逐级下放审批权。

对应到类加载:加载一个类时,先让父加载器尝试加载,父加载器加载不了,子加载器才自己加载

二、双亲委派机制的官方定义(人话翻译)

当一个类加载器收到类加载请求时,它首先不会自己去尝试加载这个类,而是把请求委派给父类加载器去完成,每一个层次的类加载器都是如此,直到请求到达顶层的启动类加载器;如果父加载器无法加载这个类(在自己的搜索范围内找不到该类的字节码),子加载器才会尝试自己去加载。

  • 关键术语翻译:
    • “双亲”≠“父母”:指「父加载器」(优先级更高的加载器),不是真的有两个加载器;
    • “委派”:就是“上报、转交”的意思。

三、双亲委派的完整流程(以加载com.ai.HelloWord为例)

我们以加载你自己写的com.ai.HelloWord类、以及核心类java.lang.String为例,拆解完整流程,直观看到“委派-回退”的逻辑:

场景1:加载你自己的类 com.ai.HelloWord

不能(rt.jar里没有这个类)

不能(ext目录没有这个类)

应用类加载器收到请求:加载com.ai.HelloWord

先委派给父加载器:扩展类加载器

扩展类加载器委派给父加载器:启动类加载器

启动类加载器能加载吗?

回退给扩展类加载器

扩展类加载器能加载吗?

回退给应用类加载器

应用类加载器自己加载:从当前目录找HelloWord.class

加载成功

场景2:加载核心类 java.lang.String

能(rt.jar里有String.class)

应用类加载器收到请求:加载java.lang.String

委派给扩展类加载器

委派给启动类加载器

启动类加载器能加载吗?

启动类加载器直接加载

加载成功,不会回退给子加载器

四、为什么要设计双亲委派机制?(核心作用,新手必懂)

这个机制的核心目的是保证Java类的“安全”和“唯一”,避免两个致命问题:

1. 防止核心类被篡改(安全)

如果没有双亲委派,你可以自己写一个java.lang.String类,应用类加载器直接加载,就会替换JDK的核心String类——这会导致整个Java体系崩溃(比如字符串处理全乱了)。

有了双亲委派:你请求加载java.lang.String时,会优先委派给启动类加载器,它会加载rt.jar里的“正版”String类,你自己写的“盗版”String类永远不会被加载,从根源上保证核心类的安全性。

2. 保证类的唯一性(避免重复加载)

不管哪个类加载器请求加载java.lang.String,最终都是启动类加载器加载,整个JVM中只有一个String.class对象——如果不同加载器加载出多个String类,会导致“两个String对象明明内容一样,却判定为不同类型”(比如new String() instanceof String返回false),程序完全无法正常运行。

3. 简化类加载器的分工(高效)

每个加载器只负责自己的“管辖范围”:

  • 启动类加载器:只管核心类(rt.jar);
  • 扩展类加载器:只管扩展类(ext目录);
  • 应用类加载器:只管用户类/第三方jar;
    不用重复扫描,加载效率更高。

五、补充:打破双亲委派的场景(新手了解即可)

并不是所有场景都遵循双亲委派,有些特殊需求会“打破”这个规则(比如Tomcat、Spring):

  • Tomcat:为了让不同Web应用加载自己的类(比如两个应用用不同版本的Spring),Tomcat自定义了类加载器,优先自己加载,再委派父加载器;
  • 热部署:比如Spring Boot的热更新,需要重新加载类,也会绕过双亲委派;
  • 核心逻辑:打破双亲委派的本质是「重写类加载器的loadClass方法」,改变“先委派父加载器”的顺序。

总结(核心要点回顾)

  1. 核心逻辑:类加载请求“先向上委派父加载器,父加载器加载不了,子加载器再自己加载”;
  2. 核心作用
    • 安全:防止核心类(String、System)被篡改;
    • 唯一:保证全JVM中核心类只有一个版本;
  3. 类比记忆:像公司审批流程,小事逐级上报,大事由高层定,底层只处理高层不管的事;
  4. 例外场景:Tomcat等容器为了隔离应用,会打破双亲委派,优先加载自己的类。

如果想直观验证双亲委派,你可以写一个自定义类加载器,重写loadClass方法,打印加载流程,就能看到“先委派父加载器,再自己加载”的过程~

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐