☰
【JVM】从堆、栈到 GC 垃圾回收器,后端面试核心知识点全梳理
2026/10/14 4:50:49 网站建设 项目流程

前言

Java 最核心的特性:一次编译,到处运行,底层就是 JVM(Java Virtual Machine,Java 虚拟机)。 我们写的.java源码编译成.class字节码,JVM 加载字节码,解释 / 编译执行。JVM 屏蔽了操作系统底层差异。

学习 JVM,两大核心板块:

  1. 内存模型(运行时数据区):重点搞懂虚拟机栈、堆
  2. GC 垃圾回收:什么时候回收、回收什么对象、用什么收集器回收

重要区分:JVM 内存 ≠ 操作系统内存;GC 只回收堆内存中的对象,栈不存在 GC。

一、JVM 运行时数据区

JVM 在执行 Java 程序时,会把管理的内存划分为若干不同区域,部分区域随 JVM 启动创建,JVM 退出销毁;部分区域线程私有,随线程创建销毁。

线程私有区域(每个线程独立一份,互不共享)

  1. 虚拟机栈(Java 栈)
  2. 本地方法栈
  3. 程序计数器

线程共享区域(整个 JVM 一份,所有线程共享)

  1. 堆(Heap)【GC 主战场】
  2. 方法区(元空间 MetaSpace,JDK8+)

✅ 一句话区分私有 / 共享:线程私有,线程生则生、线程亡则释放;共享区域由 JVM 统一管理,涉及 GC。

1. 虚拟机栈(重点)

栈:存放局部变量、方法调用栈帧

  • 每个 Java 方法执行时,都会创建一个栈帧 (Stack Frame),压入虚拟机栈;方法执行结束,栈帧出栈销毁。
  • 栈帧包含:局部变量表、操作数栈、动态链接、方法返回地址。
  • 局部变量表存放:基本数据类型(int/boolean/long)、对象引用地址(不是对象本身!对象本体在堆)。

栈上存的是对象引用,对象实例本身一定在堆。

栈的异常:

  • StackOverflowError:栈深度超过上限(无限递归)
  • OutOfMemoryError:不断创建线程,耗尽操作系统内存(线程栈分配内存失败)

栈内存没有 GC!方法执行完毕,栈帧自动弹出释放,不需要垃圾回收。

2. 程序计数器

当前线程执行字节码的行号指示器。记录当前执行到哪一行指令。 唯一一个没有 OOM的区域。

3. 本地方法栈

和虚拟机栈作用几乎一致,区别是为native 本地方法(C/C++ 写的方法)服务。

4. 堆 Heap(重中之重,GC 发生在这里)

堆:存放所有对象实例 + 数组,JVM 最大一块内存,所有线程共享,GC 主要回收区域。

JDK8 堆结构划分:

  • 新生代(Young Generation):新创建对象,绝大多数对象朝生夕灭。
    • Eden 区:对象诞生地,占新生代 80%
    • Survivor0(S0,From)、Survivor1(S1,To):各占 10%

    同一时间只有一块 Survivor 是可用的,另一块是空的,复制算法。

  • 老年代(Old Generation):存活时间久、大对象。多次 GC 存活的对象晋升到老年代。

对象晋升规则:对象在新生代熬过多次 GC(默认 15 次),晋升到老年代;超大对象直接进入老年代,避免 Eden 复制开销。

堆会发生 OOM:java.lang.OutOfMemoryError: Java heap space,堆内存不足。

5. 方法区(JDK1.8 之后叫元空间 MetaSpace)

存放:类的结构信息、常量、静态变量、即时编译器编译后的代码。

JDK1.7 及之前:永久代(PermGen),属于堆内存一部分,容易 OOM; JDK8:移除永久代,改为元空间,使用操作系统本地内存,不再占用堆内存。

二、栈和堆核心对比(面试必问)

表格

对比项虚拟机栈堆 Heap
归属线程私有,每个线程独立栈线程共享,全局唯一堆
存储内容栈帧、局部变量、对象引用地址对象实例、数组
内存回收自动出栈销毁,无 GC由 GC 垃圾收集器回收废弃对象
大小固定栈深度,一般不大JVM 最大内存区域,可通过 - Xmx -Xms 设置
OOM 类型StackOverflowError / OOM(创建过多线程)OOM: Java heap space
生命周期跟随线程跟随 JVM

高频误区纠正: ❌ 错误说法:对象存在栈里。 ✅ 正确:对象实例一定在堆;栈里面只保存对象的引用地址。 JDK 有逃逸分析,满足条件的小对象会发生栈上分配,属于优化,不是默认行为。面试没特别问逃逸分析,优先按基础结论作答。

三、垃圾回收 GC 基础理论

GC 目标:回收堆中不再被引用的对象,释放内存。

1. 如何判断对象是否可以回收

引用计数法

对象被引用 + 1,引用消失 - 1;计数 = 0 则可回收。 缺点:循环引用无法识别,JVM 没有采用。

可达性分析算法(JVM 采用)

以GC Roots作为起始根节点,向下搜索引用链。不能到达的对象,判定为垃圾,可以回收。

GC Roots 包含:

  1. 虚拟机栈中局部变量引用的对象
  2. 方法区静态变量引用对象
  3. 本地方法栈 native 引用对象
  4. 锁持有的对象

即使对象不可达,也不是立刻死亡,会执行一次 finalize ()(基本不推荐使用,该方法不可靠)。

2. 三大基础垃圾回收算法

  1. 标记 - 清除(Mark-Sweep)标记垃圾,统一清除。缺点:内存碎片,分配大对象容易 OOM。
  2. 复制算法(Copying)新生代使用。Eden + Survivor,存活对象复制到空白 Survivor,清空原区域。无碎片;代价是占用双倍内存。
  3. 标记 - 整理(Mark-Compact)老年代使用。标记垃圾,清除后,存活对象向一端移动压缩,消除内存碎片。移动对象,STW 时间更长。

新生代:复制算法;老年代:标记清除 / 标记整理。

3. STW Stop The World

GC 执行时,暂停所有用户线程。

所有 GC 收集器都会 STW,差别只是 STW 时间长短。低延迟收集器目标就是减少 STW 时长。

四、主流垃圾收集器(GC 收集器)详解

收集器就是 GC 算法的具体落地实现。不同收集器作用于新生代 / 老年代,搭配使用。

1. Serial 收集器(串行收集器)

  • 单线程收集,GC 时唯一 GC 线程工作,所有用户线程 STW。
  • 新生代:复制算法;老年代 Serial Old:标记整理。
  • 适用:客户端、单机小内存程序。
  • 缺点:STW 时间长,大内存场景卡顿明显。
  • 参数:-XX:+UseSerialGC

2. Parallel 收集器(并行收集器,JDK8 默认)

  • 多线程 GC,STW,多条 GC 线程并行工作,充分利用多核 CPU。
  • Parallel Scavenge(新生代复制) + Parallel Old(老年代标记整理)
  • 目标:吞吐量优先(吞吐量 = 用户运行时间 / 总运行时间)。牺牲停顿时间,追求最大吞吐量。
  • 适合:后台任务、批量计算,对停顿不敏感的服务。
  • 参数:-XX:+UseParallelGC

3. CMS 收集器(Concurrent Mark Sweep,并发标记清除)

JDK1.5 推出,JDK9 开始废弃,JDK14 移除

  • 老年代收集器,并发标记 + 并发清除,尽量和用户线程一起跑,降低 STW。
  • 算法:标记清除,会产生内存碎片。
  • 目标:低延迟,减少停顿时间
  • 执行四阶段:
    1. 初始标记(STW,很短)
    2. 并发标记(用户线程同时运行)
    3. 重新标记(STW)
    4. 并发清除(用户线程同时运行)
  • 缺点:内存碎片、CPU 占用高、浮动垃圾,无法处理老年代快速增长。

4. G1 收集器(Garbage-First,JDK9 默认)

面向服务端,吞吐量与低延迟之间平衡,JDK8 推荐,JDK9 默认 核心思想:化整为零,将整个堆划分为多个大小相等的 Region 区域,Eden、Survivor、老年代不再连续。

  • 可以同时管理新生代 + 老年代。
  • 优先回收垃圾最多的 Region(Garbage First)。
  • 算法:标记整理 + 复制,不会产生大量内存碎片
  • 支持可预测停顿,可设置预期最大停顿时间-XX:MaxGCPauseMillis
  • 阶段:初始标记 (STW)、并发标记、最终标记 (STW)、筛选回收 (STW)
  • 参数:-XX:+UseG1GC

5. ZGC(Z Garbage Collector,JDK11 引入,JDK17 稳定)

超低延迟收集器,大内存首选

  • 几乎可以做到亚毫秒级 STW,堆可以支持 TB 级别。
  • 着色指针 + 读屏障实现并发标记、并发整理。
  • 目标:STW 时间几乎不随堆内存变大而增长。
  • 适用:高并发、低延迟业务,网关、订单核心服务。
  • 参数:-XX:+UseZGC

6. Shenandoah 收集器

RedHat 开发,和 ZGC 类似,低延迟,并发整理,JDK12 引入。国内使用少于 ZGC。

五、GC 收集器对比汇总表

表格

收集器作用区域算法特点目标场景
Serial新 + 老复制 / 标记整理单线程,STW 长小应用,客户端
Parallel新 + 老复制 / 标记整理多线程,吞吐量优先后台批量任务,JDK8 默认
CMS老年代标记清除并发,低延迟,内存碎片JDK8 老项目,现已淘汰
G1整个堆标记整理 + 复制分区模型,可控停顿,无碎片中小型服务,JDK9 默认
ZGC整个堆并发标记整理亚毫秒级 STW,支持超大堆高并发核心业务,低延迟

六、JVM 启动常用参数

# 堆初始大小,最大堆 -Xms2g -Xmx2g # 指定GC收集器示例 -XX:+UseG1GC -XX:+UseZGC # 打印GC日志 -XX:+PrintGCDetails

七、面试高频考点

  1. 堆和栈存放什么?对象在哪?

栈存放局部变量、对象引用;堆存放对象实例;栈不存在 GC,堆由 GC 回收。

  1. 可达性分析是什么,GC Roots 有哪些?

从 GC Roots 向下遍历引用链,不可达对象判定垃圾;列出 4 类 GC Roots。

  1. CMS、G1、ZGC 核心区别?

CMS:老年代,标记清除,有碎片;G1 分区化,平衡吞吐量和延迟;ZGC 超低 STW,适合大堆低延迟场景。

  1. STW 是什么?所有 GC 都会 STW 吗?

用户线程暂停;全部收集器都有 STW,只是停顿长短不同。

  1. 新生代 GC(Minor GC)、老年代 GC(Full GC)

Minor GC:新生代满触发,频率高,速度快;Full GC:整堆回收,STW 时间很长,尽量避免。

补充:Major GC 指老年代 GC,经常伴随 Full GC。

八、总结

  1. 虚拟机栈是线程私有,存引用,方法结束自动释放,没有 GC;堆是共享区域,存放对象实例,GC 回收堆内存。
  2. JVM 使用可达性分析判定垃圾,三大基础 GC 算法:复制、标记清除、标记整理。
  3. Parallel:吞吐量优先;CMS(淘汰)追求低延迟,有碎片;G1 分区,平衡方案;ZGC 亚毫秒级低延迟,现代业务首选。
  4. 线上调优核心目标:尽量减少 Full GC,降低 STW 停顿。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询