JVM内存结构、类加载、GC基础速览

一、JVM 五大内存区域

1. 程序计数器

  • 唯一没有OOM的内存区域

  • 核心作用:记录当前线程执行的字节码行号,用于线程切换后恢复执行位置

  • 线程私有,生命周期跟随线程

2. 虚拟机栈(Java栈)

  • 线程私有,每个方法被调用时,都会创建一个独立栈帧

  • 栈帧组成:局部变量表、操作数栈、动态链接、方法出口

  • 异常类型:栈深度超限 → StackOverflowError;内存资源耗尽 → OOM

3. 本地方法栈

  • 结构、特性与虚拟机栈一致,专门为 Native 本地方法 提供运行空间

  • 线程私有,可抛出栈溢出异常、OOM异常

4. 堆 Heap

  • 线程共享,JVM 最大的内存区域

  • 核心作用:存储所有 Java 对象实例、数组,是 GC 垃圾回收的核心区域

  • 内存分区:新生代、老年代,频繁发生 Heap OOM

5. 方法区(元空间)

  • 线程共享

  • 存储内容:类结构信息、运行时常量、静态变量、即时编译代码

  • 版本差异:JDK8 之前为永久代,JDK8 彻底替换为元空间(使用本地物理内存)

  • 存在内存溢出,会触发 OOM

二、对象创建、内存分配、TLAB

1. 对象创建完整流程

  1. 类校验:检查对应类是否已完成加载、解析、初始化

  2. 内存分配:根据堆内存规整度,选择指针碰撞/空闲列表分配内存

  3. 对象头初始化:填充 MarkWord、类型指针等对象头信息

  4. 属性默认初始化:为对象实例变量赋予系统默认初始值

  5. 构造方法初始化:执行构造函数,赋值自定义初始值

  6. 返回当前对象引用

2. 堆内存两种分配方式

  • 指针碰撞:堆内存规整有序,通过移动分界指针完成内存分配,效率高

  • 空闲列表:堆内存存在大量碎片,JVM 维护空闲内存列表,筛选可用空间分配

3. 对象内存分配规则

  • 普通小对象:优先分配在新生代 Eden 区
  • 大对象:直接进入老年代(避免新生代频繁复制)
  • 长期存活对象:达到晋升年龄,从新生代移入老年代

3. TLAB(本地线程分配缓冲)

  • 定义:Eden区为每个线程单独分配的一小块私有内存区域
  • 目的:解决多线程并发创建对象的锁竞争问题,提升分配效率
  • 原理:线程优先在自己的TLAB内创建对象,无需竞争锁;TLAB空间耗尽再共享Eden区
  • 特点:默认开启,是JVM层面优化并发对象分配的核心手段

三、类加载 7 个阶段

完整顺序:加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载

  1. 加载:读取 class 文件二进制数据,载入内存,生成运行时类数据结构

  2. 验证:包含格式、元数据、字节码、符号引用校验,保证 class 文件安全合法

  3. 准备:为类静态变量分配内存,赋予系统默认初始值

  4. 解析:将代码中的符号引用替换为直接内存地址引用

  5. 初始化:执行静态代码块、为静态变量赋予自定义初始值,仅首次主动使用类时触发

  6. 使用:程序正常调用类的属性、方法,执行业务逻辑

  7. 卸载:类无任何有效引用,满足回收条件,被 JVM 卸载销毁

四、双亲委派模型

1. 执行流程

子类加载器接收类加载请求 → 优先向上委托父加载器加载 → 顶层启动类加载器无法完成加载 → 由当前子类加载器自行加载。

2. 核心优势

  • 沙箱安全:防止自定义类篡改JDK核心类库

  • 类唯一性:避免类重复加载,节省内存资源

3. 破坏双亲委派常见场景

  • JDBC SPI 服务提供者加载机制

  • Tomcat 自定义类加载器(实现多应用依赖隔离)

  • 热部署、热修复框架

核心原理:打破向上委派规则,子类加载器优先自主加载类文件

五、GC Roots 可回收判定根节点(必背)

JVM 通过可达性分析判断对象存活,起始节点即为GC Roots,包含五类:

  1. 虚拟机栈中引用的对象(方法局部变量)
  2. 本地方法栈 Native 引用的对象
  3. 方法区中静态属性、常量引用的对象
  4. 系统内所有活跃线程持有的对象
  5. synchronized 同步锁持有的对象

六、三大垃圾回收算法

1. 标记-清除

  • 原理:遍历内存标记所有存活对象,统一清除未标记垃圾对象
  • 优点:算法简单,无需移动对象
  • 缺点:产生大量内存碎片,可能导致大对象分配失败
  • 适用:老年代

2. 标记-复制

  • 原理:将内存划分为两块对等区域,标记存活对象后,复制到空白区域,清空原内存区域
  • 优点:无内存碎片,内存分配效率极高
  • 缺点:永久浪费 50% 内存空间,对象复制存在开销
  • 适用:新生代(对象存活率低)

3. 标记-整理

  • 原理:标记存活对象后,将所有存活对象向内存一端压缩整理,清空末端垃圾内存
  • 优点:无内存碎片,内存利用率高
  • 缺点:需要移动大量存活对象,GC 耗时高、开销大
  • 适用场景:老年代

七、分代回收思想 & 新生代/老年代特性

1. 分代回收核心思想

根据对象存活周期不同,将堆内存分为新生代、老年代,针对不同生命周期对象使用不同GC算法,最大化回收性能、降低STW耗时。

2. 新生代特性

  • 分为:1块Eden区 + 2块Survivor区(S0/S1),比例 8:1:1
  • 对象存活率低、创建销毁频繁
  • 使用标记复制算法
  • 触发 Minor GC,频率高、速度快

3. 老年代特性

  • 存放长期存活对象、大对象
  • 对象存活率极高、极少消亡
  • 使用标记清除/标记整理算法
  • 触发 Major GC/Full GC,频率低、耗时极高

八、对象晋升规则 + 动态年龄判断

1. 标准晋升规则

对象在新生代熬过一次Minor GC,年龄+1;默认年龄达到 15岁,晋升到老年代。

2. 动态年龄判断(面试高频)

并非必须等到15岁:新生代相同年龄所有对象总大小,大于Survivor区域一半,当前年龄及以上对象,直接全部晋升老年代。

目的:提前回收长期存活对象,避免Survivor区内存溢出。

3. 特殊晋升规则

  • 超大对象:直接进入老年代,不经过新生代
  • Minor GC后存活对象大于Survivor剩余空间,直接晋升老年代

九、永久代 & 元空间区别

1. 永久代(JDK1.7及之前)

  • 属于JVM堆内存一部分,受JVM堆参数限制
  • 容易发生永久代OOM
  • 存放类元信息、常量、静态变量

2. 元空间(JDK1.8+)

  • 使用操作系统本地物理内存,不在堆内
  • 默认无固定大小限制,极大减少OOM概率
  • 只存类元数据,静态变量、字符串常量池移入堆内存

3. 核心区别

永久代占用堆内存、容易OOM;元空间使用本地内存,内存充足、稳定性更高。

十、元空间 OOM 场景

元空间虽然使用本地内存,但以下场景依然会出现 Metaspace OOM

  1. 动态频繁生成大量类:热部署、动态代理、CGLIB循环生成代理类
  2. 大量动态脚本执行:Groovy、规则引擎频繁编译脚本生成新类
  3. 循环加载大量外部类:代码中无限循环加载class文件,类无法卸载
  4. 人为限制元空间最大值:配置 MaxMetaspaceSize 过小,内存耗尽
  5. 类加载器不回收:自定义类加载器常驻内存,导致类无法卸载,元数据持续堆积

七、题纲

  1. JVM 五大内存区域:组成、线程属性、核心作用、是否会 OOM
  2. 类加载 7 个阶段及每个阶段核心职责
  3. 双亲委派流程、优势、破坏场景
  4. 5 类 GC Roots
  5. 三种 GC 算法的原理、优缺点、适用内存区域
  6. 分代回收思想 & 新生代/老年代特性
  7. 对象晋升规则 + 动态年龄判断
  8. 永久代 & 元空间区别
  9. 元空间 OOM 场景