04_JVM 类加载子系统与内存结构

双亲委派层级 自下而上









类加载请求
双亲委派模型
父加载器能否加载?
父加载器完成加载
当前加载器执行加载
验证:字节码合法性检查
准备:静态变量分配内存+默认值
解析:符号引用→直接引用
是否主动引用?
初始化:执行
类就绪(未初始化)
使用:实例化/方法调用
类可卸载?
卸载:Class对象回收
启动类加载器
扩展类加载器
应用类加载器
自定义加载器

类加载子系统是 JVM "加载代码" 的核心,内存结构(运行时数据区)是 JVM "运行代码" 的载体 ------ 前者负责把.class文件搬进 JVM,后者负责存放和执行,二者是 JVM 运行的 "左膀右臂"。

一、 类加载子系统

核心:把.class 文件变成 JVM 可执行的类

1. 核心职责

将外部.class二进制字节流加载到 JVM,经校验 / 转换 / 初始化后,生成Class对象作为访问入口,最终形成可执行的 Java 类型。

2. 类加载完整生命周期(7 阶段)

核心流程为:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载,其中前 5 阶段是类加载子系统的核心

阶段 核心工作 新手必记关键细节(易混点)
加载 1. 按全限定名获取.class 字节流 2. 转成方法区运行时数据结构 3. 堆中生成Class对象(访问入口) Class对象存在 ,类元数据存在方法区 / 元空间 ✅ 字节流来源:本地文件、jar、网络、动态代理等
验证 确保.class 文件符合 JVM 规范,无安全风险 ✅ 4 层验证:文件格式(魔数 0xCAFEBABE)→ 元数据 → 字节码 → 符号引用 ✅ 核心目的:防止恶意字节码破坏 JVM
准备 为静态变量分配内存 + 设置默认初始值 ✅ 仅处理静态变量,实例变量在对象实例化时分配(堆) ✅ final 静态变量:直接赋值常量值(而非默认值) ✅ 示例:static int a=10 → 准备阶段 a=0,初始化阶段 a=10
解析 常量池符号引用 → 直接引用(内存地址 / 偏移量) ✅ 符号引用:字符串描述(如 "java.lang.String") ✅ 直接引用:指向目标的实际内存地址
初始化 执行类初始化方法<clinit>() <clinit>()由编译器生成,包含静态变量赋值 + 静态代码块 ✅ 执行顺序:父类<clinit>()先于子类 ✅ 触发场景(主动引用):new 实例、调静态方法 / 变量、反射、主类启动、子类初始化触发父类
使用 实例化对象、调用方法 / 字段 -
卸载 回收 Class 对象,释放类元数据 ✅ 仅自定义类加载器加载的类可卸载,核心类(如 java.lang.String)不会卸载

📌 核心记忆口诀(类加载 5 核心阶段):

加(加载)验(验证)准(准备)解(解析)初(初始化),

堆存 Class 元存区,静态变量先默认,final 直接赋常量。

3. 类加载器与双亲委派模型

(1)类加载器分类(4 类)
类加载器 负责加载范围 实现方式 核心特征
启动类加载器(Bootstrap) JAVA_HOME/lib 下核心类库(如 rt.jar) C++ 实现(无 Java 对应类) 最顶层,无法通过代码直接获取
扩展类加载器(Extension) JAVA_HOME/lib/ext 下扩展类库 Java 实现(继承 ClassLoader)
应用类加载器(Application) classpath 下用户自定义类 / 第三方 jar Java 实现(默认类加载器)
自定义类加载器 开发者自定义路径的类(如加密.class) 继承 ClassLoader,重写 findClass () 灵活控制加载逻辑
(2)双亲委派模型(核心:向上委派,向下加载)
核心规则(3 步)
  1. 收到加载请求 → 先委派给父加载器,不自己加载;
  2. 父加载器继续向上委派,直到启动类加载器;
  3. 父加载器加载失败 → 子加载器才自己尝试加载。
核心优势

✅ 避免类重复加载(同一个类只会被一个加载器加载);

✅ 防止核心类被篡改(如自定义java.lang.String无法加载,启动类加载器已加载核心版本)。

📌 双亲委派记忆口诀:

父上子下,先委后加;核心不篡,唯一不杂。

二、 JVM 内存结构(运行时数据区:类和对象的 "运行容器")

1. 核心划分(按线程归属)

tex 复制代码
JVM运行时数据区
├─ 线程共享区(所有线程共用,易出OOM)
│  ├─ 堆(Heap):存对象实例,GC核心区域
│  └─ 方法区(Method Area):JDK8后为元空间,存类元数据
└─ 线程私有区(每个线程独立,随线程生灭)
   ├─ 程序计数器:记录字节码执行地址
   ├─ 虚拟机栈:服务Java方法,存栈帧
   └─ 本地方法栈:服务native方法(C/C++实现)

2. 各区域详细说明

(1)线程私有区
区域 核心作用 关键特性 异常类型
程序计数器 记录当前线程执行的字节码指令地址 ✅ 唯一不会 OOM 的区域 ✅ native 方法时值为 undefined
虚拟机栈 描述 Java 方法执行,每个方法对应一个栈帧 ✅ 栈深度由 - Xss 参数设置(默认 1M) ✅ 栈帧含:局部变量表 + 操作数栈 + 动态链接 + 返回地址 StackOverflowError(栈太深)OutOfMemoryError(扩展失败)
本地方法栈 服务 native 方法 ✅ HotSpot 中与虚拟机栈合二为一 同虚拟机栈

📌 线程私有区记忆口诀:

计数器记地址,唯一无 OOM;

虚拟机栈存方法,栈帧进出控执行;

本地方法栈辅 native,异常同栈不特殊。

(2)线程共享区
① 堆(Heap):JVM 最大内存区域
核心作用

唯一目的是存放对象实例和数组,几乎所有对象都在这里分配内存。

关键特性

✅ 线程共享,GC(垃圾回收)的主要区域("GC 堆");

✅ 内存参数:-Xms(初始堆)、-Xmx(最大堆);

✅ 分代划分(GC 优化核心):

tex 复制代码
堆
├─ 新生代(Young):存新对象,GC频率高
│  ├─ Eden区(伊甸园):新对象优先分配
│  └─ Survivor区(幸存者):From/To区,存Eden存活对象
└─ 老年代(Old):存新生代多次GC存活的对象,GC频率低

✅ 异常:OutOfMemoryError: Java heap space(堆内存不足)。

📌 堆记忆口诀:

堆存对象和数组,GC 主要忙这块;

新生伊甸加幸存,老年代里存长寿。

② 方法区(JDK8 后为元空间):存类的 "说明书"
核心作用

存储类元数据(版本、字段、方法)、运行时常量池、静态变量、即时编译代码缓存。

JDK7 vs JDK8 核心变化(易考点)
版本 实现方式 内存来源 限制参数 异常类型
JDK7 及之前 永久代(PermGen) JVM 堆内存 -XX:MaxPermSize OutOfMemoryError: PermGen space
JDK8 及之后 元空间(Metaspace) 本地系统内存 -XX:MaxMetaspaceSize OutOfMemoryError: Metaspace

📌 方法区记忆口诀:

方法区存元数据,JDK8 前叫永久;

永久代在堆里限,元空间本地无上限。

三、 类加载子系统与内存结构的关联(核心逻辑)

类加载子系统加载.class
类元数据→方法区/元空间
Class对象→堆
准备阶段
静态变量→方法区/元空间(分配内存+默认值)
初始化阶段
静态变量赋值→方法区/元空间
实例化对象
对象实例→堆
对象引用→虚拟机栈局部变量表
执行方法
创建栈帧→虚拟机栈

核心关联点

  1. 类加载的最终产物:类元数据存方法区 / 元空间Class对象存
  2. 静态变量:准备阶段在方法区 / 元空间分配内存,初始化阶段完成赋值;
  3. 对象实例:分配在 中,对象引用存储在虚拟机栈的局部变量表;
  4. 方法执行:虚拟机栈中创建栈帧,局部变量、操作数等都存于栈帧内。

总结

  1. 类加载核心:加验准解初5 阶段,双亲委派 "向上委派、向下加载",核心类不被篡改;
  2. 内存结构核心:线程私有区(计数器无 OOM、虚拟机栈存方法),线程共享区(堆存对象、方法区存元数据);
  3. 版本差异核心:JDK8 移除永久代,方法区变为元空间(使用本地内存);
  4. 关联核心:类元数据存方法区,对象存堆,方法执行依赖虚拟机栈。

JVM 类加载子系统 & 内存结构 核心记忆口诀

一、 类加载子系统 记忆口诀

1. 类加载五阶段(核心流程)

口诀:加验准解初,五步按序走

解释

  • 加:加载(获取字节码,生成 Class 对象)
  • 验:验证(校验字节码合法性,防恶意篡改)
  • 准:准备(类变量赋默认值,分配内存)
  • 解:解析(符号引用转直接引用)
  • 初:初始化(执行<clinit>(),赋程序员指定值)

2. 双亲委派模型(核心规则)

口诀:父先子后找,安全防重复

解释

  • 父先子后:类加载请求先委托父加载器,父加载失败子才加载
  • 安全防重复:避免核心类(如java.lang.String)被篡改,防止类重复加载
  • 层级补充:启动→扩展→应用→自定义(自顶向下委托)

3. 类初始化触发场景(主动使用)

口诀:new 调静,反射用,子类初,主类跑,枚举动

解释

  • new:new 关键字创建对象
  • 调静:调用类的静态方法 / 非 final 静态变量
  • 反射用:通过反射操作类的属性 / 方法
  • 子类初:初始化子类时,父类先初始化
  • 主类跑:执行 main 方法的程序入口类
  • 枚举动:JDK8 + 枚举类 / 动态语言支持(MethodHandle)

4. 打破双亲委派典型场景

口诀:Tomcat 隔离,JDBC 驱动,热部署替换

解释

  • Tomcat 隔离:不同 Web 应用加载同名类,需反向委派
  • JDBC 驱动:启动类加载器无法加载,靠线程上下文类加载器
  • 热部署替换:自定义类加载器实现类的重新加载

二、 内存结构 记忆口诀

1. JDK8 运行时数据区(区域划分)

口诀:私有三区域,共享堆与元,无一 OOM 除计数

解释

  • 私有三区域:程序计数器、虚拟机栈、本地方法栈(线程私有)
  • 共享堆与元:堆(存对象)、元空间(存类元信息,替代永久代)
  • 无一 OOM 除计数:只有程序计数器不会 OOM,其余区域均可能出现 OOM

2. 堆内存分代模型(核心设计)

口诀:堆分新老代,新生 811,Minor 勤 Major 稀

解释

  • 新老代:新生代(存新生对象)+ 老年代(存存活久的对象)
  • 新生 811:新生代 = Eden(8): Survivor0(1): Survivor1(1)
  • Minor 勤 Major 稀:新生代 GC(Minor GC)频率高,老年代 GC(Major GC)频率低

3. 永久代→元空间(核心变化)

口诀:永久代退场,元空间登场;本地内存用,大小可扩容

解释

  • 永久代退场:JDK8 移除永久代,解决固定大小易 OOM 问题
  • 元空间登场:存储类元信息,直接使用本地内存
  • 大小可扩容:通过参数-XX:MaxMetaspaceSize限制,默认无上限

4. 虚拟机栈栈帧组成

口诀:局变操作数,动态链接址,附加信息补

解释

  • 局变:局部变量表(存方法参数、局部变量)
  • 操作数:操作数栈(字节码指令执行的临时数据区)
  • 动态链接址:动态链接(指向常量池的方法引用)+ 方法返回地址
  • 附加信息补:虚拟机实现相关的附加信息

三、 综合记忆口诀

类加载五步走,双亲委派父先求;

内存五区分私共,堆分代来 GC 优;

永久退场元空间,OOM 排查按区究。

相关推荐
Serene_Dream5 小时前
Java 垃圾收集器
java·jvm·面试·gc
weisian1515 小时前
JVM--6-深入JVM栈内存:方法调用的执行舞台
jvm·栈帧·栈内存
Serene_Dream5 小时前
Java 内存区域
java·jvm
star12586 小时前
数据分析与科学计算
jvm·数据库·python
2301_822382766 小时前
使用Python进行网络设备自动配置
jvm·数据库·python
m0_706653236 小时前
Python深度学习入门:TensorFlow 2.0/Keras实战
jvm·数据库·python
hgz07107 小时前
可达性分析算法
jvm·可达性算法
weisian1517 小时前
JVM--5-深入 JVM 方法区:类的元数据之家
jvm·元空间·方法区
橘橙黄又青7 小时前
JVM实践
jvm