JVM原理(JDK8~25)内存结构、分代模型、GC算法、垃圾收集器、元空间、双亲委派机制

约 5204 字

JVM完整原理(JDK8~25)内存结构、分代模型、GC算法、垃圾收集器、元空间、双亲委派机制

本文覆盖JVM整体结构、运行时数据区、JDK8内存大改版、分代模型、四大GC核心算法、主流垃圾收集器、元空间原理、双亲委派机制+破双亲委派,适配JDK8至最新JDK25版本特性,全部为一线面试高频原题,可直接背诵。

一、JVM整体架构(四大核心模块)

Java虚拟机是跨平台执行引擎,整体分为四大核心模块,所有Java代码运行都依赖该体系,整体结构如下:

JVM整体架构
├─ 类加载子系统 → 加载、验证、准备、解析、初始化(双亲委派)
├─ 运行时数据区 → 线程私有+线程共享内存区域
├─ 执行引擎     → 解释器 + JIT即时编译 + 本地方法执行
└─ GC垃圾回收子系统 → 内存分代、GC算法、垃圾收集器、内存回收
  1. 类加载子系统:加载class文件、验证、准备、解析、初始化,双亲委派机制核心载体
  2. 运行时数据区:内存模型(堆、栈、元空间等),线程私有/共享内存分区
  3. 执行引擎:解释器、JIT即时编译器、本地方法栈,执行字节码指令
  4. 垃圾回收子系统:内存分代、GC算法、垃圾收集器、内存回收、内存优化

二、JVM运行时数据区(JDK8+ 最终版内存结构)

JVM运行时数据区(JDK8+)
├─ 线程私有(线程销毁自动释放)
│  ├─ 程序计数器(无OOM)
│  ├─ 虚拟机栈(栈帧、方法执行)
│  └─ 本地方法栈(Native方法执行)
└─ 线程共享(全局常驻、GC管理)
   ├─ 堆 Heap(对象/数组、GC主战场)
   └─ 元空间 Metaspace(本地内存、存类元数据)

1. 线程私有(线程隔离、线程销毁自动释放)

  • 程序计数器 PC寄存器:唯一无OOM的区域,记录当前线程执行字节码行号,多线程切换恢复现场
  • 虚拟机栈 JVM Stack:存储栈帧(局部变量表、操作数栈、动态链接、方法出口),方法调用入栈、执行完出栈,栈深度溢出报StackOverflowError
  • 本地方法栈 Native Stack:执行native本地方法(C/C++),底层线程、IO、CAS依赖此区域

2. 线程共享(全局共享、GC管理核心)

  • 堆 Heap:所有对象、数组实例存储区域,GC唯一主战场,分年轻代、老年代
  • 元空间 Metaspace(JDK8全新):替代永久代,存储类元数据、方法信息、常量池,使用本地内存,无固定大小限制

3. JDK8 史诗级内存改动(必考)

  • JDK7及以前:存在永久代 PermGen,堆内存一部分,固定大小,极易OOM
  • JDK8 彻底移除永久代,替换为元空间 Metaspace
  • 字符串常量池、静态变量从永久代迁移至堆内存
  • 元空间使用操作系统本地内存,不再占用堆内存,默认动态扩容,大幅降低OOM概率

三、JVM分代模型(JDK8~25 统一分代)

为什么需要分代?不同对象生命周期不同,针对性GC,提升回收效率。90%对象朝生夕灭,只需频繁回收新生代,老年代低频回收。

堆内存分代结构
├─ 年轻代(占堆1/3)
│  ├─ Eden区(80%)→ 新对象默认存放
│  ├─ Survivor0(10%)
│  └─ Survivor1(10%)→ S0/S1始终一空一用
└─ 老年代(占堆2/3)→ 长期存活对象、大对象、晋升对象

对象晋升链路:Eden → Survivor循环存活15次 → 老年代

为什么需要分代?不同对象生命周期不同,针对性GC,提升回收效率。90%对象朝生夕灭,只需频繁回收新生代,老年代低频回收。

1. 年轻代(Young Generation)

  • 占堆内存 1/3,分为 Eden区 + Survive0 + Survive1
  • 比例默认:8:1:1
  • Eden:新建对象默认存放区域
  • S0/S1:存活对象复制区,始终空出一块作为备份
  • GC类型:Minor GC,频繁执行、速度快

2. 老年代(Old Generation)

  • 占堆内存 2/3
  • 存放长期存活对象、大对象、多次GC存活对象
  • GC类型:Major GC / Full GC,耗时久、频率低、尽量避免

3. 对象晋升机制

  • 对象在新生代经历 15次Minor GC存活,晋升老年代(可通过JVM参数调整)
  • 大对象直接进入老年代,避免新生代复制开销
  • 新生代空间不足,存活对象提前晋升老年代

四、四大GC核心算法(底层原理)

GC算法适配结构图
├─ 标记清除:标记存活 → 清空死亡 → 产生内存碎片
├─ 标记复制:标记存活 → 复制新区域 → 清空原区域(无碎片、耗空间)
├─ 标记整理:标记存活 → 压缩规整 → 清空末端(无碎片、速度慢)
└─ 分代收集(综合最优)
   ├─ 年轻代 → 标记复制算法
   └─ 老年代 → 标记清除 + 标记整理算法

1. 标记清除算法(Mark-Sweep)

  • 流程:标记存活对象 → 清除死亡对象
  • 优点:无需移动对象,速度较快
  • 缺点:产生大量内存碎片,大对象无法分配,容易触发Full GC

2. 标记复制算法(Mark-Copy)

  • 流程:标记存活对象 → 复制到空闲区域 → 清空原区域
  • 优点:无内存碎片,内存规整
  • 缺点:浪费一半内存空间,适合存活对象少的区域
  • 适用:年轻代默认算法(存活对象少、回收频繁)

3. 标记整理算法(Mark-Compact)

  • 流程:标记存活对象 → 向一端整理压缩 → 清空末端内存
  • 优点:无内存碎片、不浪费空间
  • 缺点:需要移动对象,效率低
  • 适用:老年代默认算法(存活对象多、不适合复制)

4. 分代收集算法(GC综合算法)

  • JVM终极算法,整合以上三种
  • 年轻代:标记复制
  • 老年代:标记清除 + 标记整理

五、主流垃圾收集器(JDK8~25 版本迭代)

垃圾收集器迭代演进 & 特性
├─ Parallel GC(JDK8默认)→ 高吞吐、长停顿、后台批量任务
├─ CMS(JDK8废弃/JDK14移除)→ 低并发停顿、碎片多、浮动垃圾
├─ G1 GC(JDK9默认)→ 分区回收、吞吐延迟平衡、大堆适配
└─ ZGC/Shenandoah(JDK11+)→ 毫秒级低延迟、超大堆、高并发首选

1. Parallel GC(JDK8 默认)

  • 多线程并行回收,追求高吞吐量
  • 适合后台业务、批量任务
  • 停顿时间较长,不适合低延迟服务

2. CMS 并发收集器(JDK8废弃、JDK14移除)

  • 追求低停顿,并发回收
  • 缺点:内存碎片多、并发浮动垃圾、CPU占用高

3. G1 GC(JDK9默认,JDK8可指定)

  • 分区回收,将堆划分为多个Region
  • 兼顾吞吐量与低延迟,可设置最大停顿时间
  • 适用于大内存堆服务

4. ZGC / Shenandoah(JDK11+ 超低延迟)

  • 毫秒级停顿,几乎不卡顿
  • 超大堆适配,JDK17、JDK25持续优化
  • 互联网高并发、低延迟服务首选

5. GC迭代总结(版本演进)

ParallelGC(吞吐) → CMS(并发低延迟) → G1(平衡) → ZGC(极致低延迟)


六、元空间 Metaspace 深度原理(JDK8核心)

JDK8内存重大更替
永久代PermGen(JDK7及以前)
├─ 归属:堆内存一部分
├─ 特点:固定大小、容量小、极易OOM
└─ 存储:类元数据、常量池、静态变量

元空间Metaspace(JDK8+ 全覆盖)
├─ 归属:操作系统本地内存
├─ 特点:动态扩容、无固定上限、无OOM风险
└─ 存储:类/方法/字段元数据、运行时动态类信息

1. 永久代与元空间核心区别

  • 永久代PermGen:属于堆内存、固定大小、易OOM、JDK7及以前使用
  • 元空间Metaspace:操作系统本地内存、动态扩容、无固定上限、JDK8+全覆盖

2. 元空间存储内容

  • 类、接口、注解、枚举的元数据信息
  • 方法代码、字段信息、常量池
  • 运行时动态生成的类(Spring CGLIB、动态代理)

3. 元空间优势

  • 彻底解决永久代OOM问题
  • 本地内存不占用堆空间,堆内存利用率更高
  • 动态伸缩,适配动态类加载场景

七、双亲委派机制(类加载核心)

类加载器层级 & 双亲委派流程
加载层级(从上至下)
├─ BootstrapClassLoader 启动类加载器(顶层)
├─ ExtClassLoader 扩展类加载器
└─ AppClassLoader 应用类加载器(底层)

执行流程:向上委托,向下加载
1. AppClassLoader 接收加载请求
2. 逐级向上委托至 Bootstrap
3. 上级无法加载,逐级下放由本级加载

1. 三大类加载器(层级结构)

  1. 启动类加载器 BootstrapClassLoader:顶层,加载JDK核心rt.jar等系统类
  2. 扩展类加载器 ExtClassLoader:加载JDK扩展包类
  3. 应用类加载器 AppClassLoader:加载项目自定义class、第三方依赖包

2. 双亲委派核心流程(必背)

向上委托、向下加载

  1. 应用类加载器收到加载请求,不直接加载,向上委托给扩展类加载器
  2. 扩展类加载器继续向上委托给启动类加载器
  3. 启动类加载器无法加载,向下逐层返回,由下层加载器加载

3. 核心作用(面试满分答案)

  • 沙箱安全机制:防止恶意代码覆盖JDK核心类(如自定义java.lang.String)
  • 类全局唯一:保证核心类只加载一次,避免重复加载、类冲突

4. 打破双亲委派机制(高频追问)

双亲委派是默认规则,非强制机制,可重写loadClass方法打破

经典打破场景

  • Tomcat:自定义类加载器,隔离不同web应用包冲突
  • JDBC SPI机制:线程上下文类加载器,加载厂商实现类
  • Spring、OSGI:热部署、动态加载类,打破双亲委派

八、GC核心概念区分(面试坑点)

  • Minor GC:新生代GC,频繁、速度快、无停顿或短停顿
  • Major GC:老年代GC,耗时较长
  • Full GC:整堆GC(新生代+老年代+元空间),STW全局暂停,线上绝对避免
  • STW:Stop-The-World,GC期间暂停所有用户线程,是卡顿的根本原因

九、终极面试背诵总结(极简版)

  • JVM结构:类加载、运行时数据区、执行引擎、GC四大模块
  • 内存分区:线程三私两共,JDK8废除永久代,改用本地内存元空间
  • 分代模型:新生代8:1:1复制算法,老年代标记整理算法,对象随GC次数晋升
  • GC算法:清除有碎片、复制耗空间、整理无碎片效率低,分代收集综合最优
  • 收集器迭代:Parallel吞吐、G1平衡、ZGC超低延迟
  • 双亲委派:向上委托保证安全和类唯一,Tomcat/SPI可打破实现隔离与动态加载