JVM 垃圾回收机制深度解析(含图解)

发布于:2025-05-22 ⋅ 阅读:(20) ⋅ 点赞:(0)

JVM 垃圾回收机制深度解析(含图解)

一、垃圾回收整体流程

垃圾回收图解

新生代Eden区
Eden区满
达到阈值
未达阈值
老年代空间不足
程序运行
内存分配
对象创建
对象进入Eden区
新生代GC触发条件
Minor GC(新生代回收)
存活对象复制到Survivor区
对象年龄增加
对象晋升到老年代
对象留在Survivor区
继续使用Survivor区
对象进入老年代
老年代GC触发条件
Major GC/Full GC(老年代回收)
标记-清除/整理老年代对象
释放内存

二、垃圾对象判定算法

以下是对 JVM 垃圾回收机制的进一步细化,结合图解直观展示核心概念、算法流程及收集器工作原理:

2.1 引用计数法(Reference Counting)

对象创建
分配内存并初始化引用计数为1
其他对象引用该对象
引用计数+1
引用失效(如指针指向其他对象)
引用计数-1
引用计数是否为0?
对象标记为垃圾,等待回收
对象存活,继续使用
垃圾回收器回收内存
重置内存状态

原理:

  • 计数器管理

    • 每个对象内嵌一个引用计数器(Reference Counter)
    • 引用增加(如被赋值、作为参数传递) → 计数器+1
    • 引用失效(如变量置空、离开作用域) → 计数器-1
  • 回收条件

    • 当计数器归零时,对象立即被标记为垃圾并回收。

循环引用问题: 无法处理循环引用(如对象 A 和 B 互相引用),导致内存泄漏。

A对象
B对象

场景描述:

对象A和B互相引用,导致两者的引用计数始终≥1,即使它们已不被其他对象引用,也无法被回收。

后果:内存泄漏(Memory Leak)。

解决循环引用的常见方法

  • 弱引用(Weak Reference)

    弱引用不增加对象的引用计数(如Java的WeakReference、Python的weakref)。

  • 手动断开引用

    开发者需在代码中显式解除循环引用(易出错,不推荐)。

  • 周期检测算法

    定期运行垃圾回收器检测循环引用(如Python的gc模块)。

2.2 可达性分析(Reachability Analysis)

可达性分析示意图

可达性分析完整流程
三色标记法核心流程
白色
灰色/黑色
所有堆对象初始为白色(隐式状态)
开始可达性分析
标记GC Roots
遍历GC Roots直接引用对象
标记完成
所有白色对象为垃圾
执行垃圾回收
灰色队列是否为空?
标记直接引用对象为灰色
取出一个灰色对象
遍历其引用的子对象
子对象颜色?
标记子对象为灰色并加入队列
跳过处理
当前对象标记为黑色

原理:
从一组称为GC Roots的根对象出发,沿着引用链向下搜索,所有能被访问到的对象视为“存活”,未被访问的则判定为“不可达”对象,可被回收。

整体结构

外层是可达性分析的主流程,内嵌三色标记法的核心流程。

颜色标识统一:

白色:未处理或可回收对象(最终状态)
灰色:待处理对象(中间状态)
黑色 :已处理完成对象
红色边框:垃圾回收相关操作

关键流程节点

  • 标记GC Roots: 从GC Roots(根对象如栈、静态变量等)出发,递归遍历所有可达对象,将其直接引用的堆对象标记为灰色,并加入灰色队列,作为标记的起点。

  • 三色标记循环: 通过灰色队列迭代处理对象(白→灰→黑状态变化)。

    1. 取出灰色对象,遍历其引用的子对象;
    2. 若子对象为白色,标记为灰色并加入队列;
    3. 当前灰色对象标记为黑色;
    4. 重复直到灰色队列为空。”
  • 回收阶段: 清除所有白色(未被标记/不可达)对象,释放内存。

颜色状态映射

存活对象标记流程:
GC Roots → 白色 → 灰色 → 遍历引用 → 黑色(完成)

垃圾对象:
始终为白色 → 最终被回收

可达性分析的优势

  • 解决循环引用问题
    即使对象A和对象B互相引用,若它们无法从GC Roots到达,仍会被判定为垃圾。

  • 高效性
    通过引用链遍历,仅需处理存活对象,避免全堆扫描。

并发标记的挑战
在并发标记阶段(如CMS、G1回收器),用户线程可能修改对象引用,导致两种问题:

  • 漏标(Missing Mark)

    • 增量更新(Incremental Update)

      若黑色对象新增对白色对象的引用,将该黑色对象重新标记为灰色(需重新扫描)。

    • 原始快照(SATB)

      记录标记开始时的对象引用关系,后续新增的引用关系视为“待处理”。

  • 多标(Floating Garbage)

    • 已标记为存活的对象被用户线程置为不可达(通常容忍到下次GC处理)。

GC Roots 包括:
1. 虚拟机栈(Java 栈)中的引用对象
来源: 方法执行时,栈帧中局部变量表存储的引用类型变量。
生命周期: 与方法调用绑定,方法结束后引用失效。
示例:

public void method() {
    Object obj = new Object(); // obj 是 GC Root
    // 方法执行期间,obj 引用的对象不会被回收
} // 方法结束后,obj 出栈,对象失去 GC Root 引用

2. 方法区中静态属性引用的对象
来源: 类的静态变量(static 字段)。
生命周期: 与类的生命周期相同,类卸载前始终存活。
示例:

public class MyClass {
    static Object staticObj = new Object(); // staticObj 是 GC Root
}

3. 方法区中常量引用的对象
来源: 字符串常量池(String Table)、类常量(final static)等。
示例:

public class Constants {
    public static final String NAME = "Doubao"; // "Doubao" 是 GC Root
    public static final List<String> LIST = Collections.unmodifiableList(
        Arrays.asList("a", "b") // 列表对象是 GC Root
    );
}

4. 本地方法栈中 JNI 引用的对象
来源: Java 调用本地代码(如 C/C++)时,本地方法栈中保存的引用。
示例:

public native void nativeMethod(); // 本地方法可能持有对象引用,成为 GC Root

5. 其他特殊引用

  • 活动线程(Active Threads):
Thread thread = new Thread(() -> {
    Object obj = new Object(); // obj 被线程栈引用,成为 GC Root
    // ...
});
thread.start();
  • 类加载器(ClassLoader):
加载的类和静态变量。
  • JVM 内部对象:
如系统类(java.lang.Class)、异常对象(ThreadDeath)等。

GC Roots 内存示意图

GC Roots
引用
引用
引用
引用
栈帧中变量
不可达
对象1
虚拟机栈局部变量
对象2
静态变量
对象3
常量
对象4
本地方法JNI引用
对象5
活动线程
对象6
对象7
对象8
无GC Root引用的对象
垃圾回收

三、垃圾回收算法详解

3.1 标记 - 清除(Mark-Sweep)

开始GC
标记存活对象(蓝色)
清除未标记对象(灰色)
释放内存空间(白色)
产生内存碎片(不连续的绿色区域)

步骤:
1. 标记: 在堆中遍历所有对象,找出内存中需要回收的对象,并且把它们标记出来。

(蓝色为存活对象、灰色为可回收对象、白色为可用内存)
在这里插入图片描述

2. 清除: 清除掉被标记需要回收的对象,释放出对应的内存空间。

在这里插入图片描述

缺点:

  • 效率低: 两次遍历堆,耗时较长。
  • 内存碎片: 回收后产生不连续内存块,可能导致大对象分配失败。

3.2 复制(Copying)

开始GC
将Eden+Survivor区存活对象复制到To Survivor
清空Eden+From Survivor (s0)区
交换From和To Survivor (s1) 区角色
继续使用新的Eden+From区

步骤:

  • 将内存分为Eden 区和两个Survivor 区(通常比例 8:1:1)。
  • 新对象分配到 Eden 区,当 Eden 区满时触发 Minor GC。
  • 存活对象复制到 From Survivor (s0) 区,清空 Eden 区。
  • 下次 GC 时,将 Eden+ From Survivor (s0) 的存活对象复制到 To Survivor (s1) 区,清空原区域。
  • 经历多次 GC 仍存活的对象晋升到老年代。

图解:

  1. 将内存划分为两块相等的区域,每次只使用其中一块

(蓝色为存活对象、灰色为可回收对象、白色为可用内存、绿色为保留内存)
在这里插入图片描述
3. 当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。

在这里插入图片描述

优点: 无内存碎片,效率高(只需移动指针)。
缺点: 浪费 50% 内存空间(实际采用 8:1:1 比例,仅浪费 10%)。


3.3 标记 - 整理(Mark-Compact)

开始GC
标记存活对象
将存活对象向一端移动
清理边界外的内存
无内存碎片

步骤:

  • 标记: 遍历标记存活对象(灰色)。
  • 整理: 将存活对象向一端移动,直接清理边界外的内存(白色区域)。
  • 优点: 无内存碎片,适合对象存活率高的场景(如老年代)。

图解:

  1. 标记过程仍然与 标记-清除算法 一样,但是后续步骤不是直接对可回收对象进行清理,
    而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

(蓝色为存活对象、灰色为可回收对象、白色为可用内存)
在这里插入图片描述

  1. 让所有存活的对象都向一端移动,清理掉边界以外的内存。

在这里插入图片描述

缺点: 需移动对象,成本较高。


3.4 分代收集(Generational Collection)

新对象创建
分配到Eden区
Eden区满?
触发Minor GC
继续分配
存活对象复制到From Survivor (s0) 区
清空Eden区
对象年龄+1
年龄达到阈值?
晋升到老年代
对象留在From Survivor (s0) 区
下次Minor GC
存活对象复制到To Survivor (s1) 区
清空Eden+From Survivor (s0) 区
交换Survivor1和To Survivor (s1) 区角色
对象进入老年代
老年代空间不足?
触发Major GC/Full GC
标记-清除/整理老年代对象
释放老年代内存

原理:
根据对象存活周期将堆分为新生代和老年代:

  • 新生代(Eden+Survivor): 对象存活率低,采用 复制算法
  • 老年代: 对象存活率高,采用 标记 - 清除标记 - 整理算法
  • 永久代 / 元空间: 存储类信息、常量池等,GC 频率低。

四、垃圾收集器详解

以下是 Java 主流垃圾回收器的核心启动参数及典型配置
分代收集器分区收集器 分类整理:

I、分代收集器

4.1 Serial 收集器
触发GC
Stop The World
初始标记(标记GC Roots直接引用的对象)
根搜索(递归遍历所有可达对象)
标记所有存活对象
清除未标记对象(回收垃圾)
内存空间整理(可选)
恢复应用线程
继续执行用户程序

特点: 单线程,STW(Stop The World)【全程暂停用户应用程序】,采用复制算法。

算法:

  • 新生代:复制算法(Copying)
    将存活对象从Eden区和Survivor区复制到另一个Survivor区,清除未存活对象。

  • 老年代:标记-整理算法(Mark-Compact)
    标记存活对象后,整理内存空间,消除碎片。

适用场景:

单线程环境(Client 模式),客户端模式或小内存应用。

参数:

# 新生代和老年代均使用Serial(单线程)
-XX:+UseSerialGC -Xmx512m

4.2 Parallel Scavenge 收集器(吞吐量优先)
触发GC
Stop The World
多线程初始标记(标记GC Roots直接引用的对象)
多线程根搜索(并行遍历所有可达对象)
标记所有存活对象
多线程复制存活对象到Survivor区或老年代
清空Eden区和From Survivor区
交换From和To Survivor区角色
恢复应用线程
继续执行用户程序

特点: 多线程,关注吞吐量(运行用户代码时间 / 总时间)。
算法:

  • 新生代(Parallel Scavenge):复制算法
    多线程并行复制存活对象,提升回收效率。

  • 老年代(Parallel Old):标记-整理算法
    多线程并行标记和整理,减少碎片。

适用场景:

多核服务器,注重吞吐量而非低延迟。

参数


# 新生代Parallel Scavenge + 老年代Parallel Old
-XX:+UseParallelGC -XX:+UseParallelOldGC -Xmx4g 

# 控制最大停顿时间(毫秒)
-XX:MaxGCPauseMillis=100 

# 控制吞吐量目标(默认99%)(如 99 表示 1% 时间用于 GC)。
-XX:GCTimeRatio=99

# 自适应调整内存区域大小
-XX:+UseAdaptiveSizePolicy 

4.3 CMS(Concurrent Mark Sweep)收集器(低延迟)
开始CMS GC
初始标记(STW)
并发标记
重新标记(STW)
并发清除
完成GC,恢复用户线程

算法:

  • 标记-清除算法(Mark-Sweep)
  • 工作流程:
    1. 初始标记(STW,短暂):标记 GC Roots 直接关联的对象。
    2. 并发标记:与用户线程并发执行,遍历可达对象。
    3. 重新标记(STW):修正并发标记阶段因用户线程运行导致的对象引用变化(使用增量更新算法)。
    4. 并发清除:与用户线程并发未标记对象。

缺点:

  • 内存碎片: 标记-清除算法不整理内存,长期运行后碎片化严重,可能导致:

    • 晋升失败(Promotion Failed): 新生代对象无法晋升到老年代。

    • 大对象分配失败(Humongous Allocation Failure)。

    • 最终触发 Full GC,使用 Serial Old(标记-整理算法) 压缩内存。

        CMS的核心目标是通过标记-清除算法减少垃圾回收的停顿时间(STW)。
        然而,由于其设计上的限制,CMS在某些场景下触发Full GC,
        无法通过并发操作完成垃圾回收,此时JVM会触发故障回退机制(Fallback Mechanism),
        强制使用单线程的Serial Old收集器(标记-整理算法)
        进行压缩执行Full GC以恢复内存可用性。
        这种设计是CMS在低延迟与内存连续性之间的权衡。
      
  • 吞吐量下降: 并发阶段占用 CPU 资源,与用户线程竞争。

  • 无法处理突发分配压力: 并发周期中若老年代空间不足,触发并发模式失败(Concurrent Mode Failure)。

触发Full GC的场景 描述 是否使用Serial Old
并发模式失败 并发标记期间老年代空间不足
晋升失败 新生代对象晋升到老年代时因碎片无法分配
显式调用System.gc() 强制触发Full GC(默认开启ExplicitGCInvokesConcurrent可绕过此行为)
元空间耗尽 类元数据区无法扩展
其他内存分配失败 直接内存(Direct Memory)或本地内存(Native Memory)耗尽等
触发条件: 并发模式失败/晋升失败等
STW: 终止并发操作
启动Serial Old收集器
标记阶段: 标记存活对象
整理阶段: 移动对象消除碎片
清除阶段: 回收垃圾内存
恢复用户线程

适用场景

Web 服务器、需要快速响应的应用、老年代垃圾回收、追求低停顿时间。

参数

# 新生代ParNew + 老年代CMS
-XX:+UseConcMarkSweepGC -Xmx8g 

# 老年代使用70%时触发CMS(默认92%)
-XX:CMSInitiatingOccupancyFraction=70

# 控制Full GC时是否进行内存压缩(减少碎片)(标记-整理)。
-XX:+UseCMSCompactAtFullCollection

# 并行执行重新标记阶段
-XX:+CMSParallelRemarkEnabled

# 指定经过多少次Full GC后执行一次压缩(0表示每次Full GC都压缩)。
-XX:CMSFullGCsBeforeCompaction=0    

II、分区收集器

4.4 G1(Garbage-First)收集器(区域化、平衡延迟与吞吐量)
开始G1 GC
初始标记(STW)
并发标记
最终标记(STW)
筛选回收(STW)
根据停顿时间选择Region回收
复制存活对象到新Region
清空回收的Region
继续执行用户线程

算法:

  • 分区(Region)
    将堆划分为多个大小相等的 Region(如 2MB-32MB)。

  • 新生代:复制算法
    存活对象复制到Survivor Region或晋升到老年代Region。

  • 老年代:标记-整理算法
    并发标记后,优先回收垃圾最多的Region(Garbage-First),并整理Region内空间。

  • 动态分代
    每个 Region 可动态扮演 Eden、Survivor、Old 或 Humongous(大对象)。

  • 可预测停顿
    根据 Region 的回收价值(垃圾占比)排序,优先回收收益高的区域。

  • 工作流程
    1. 初始标记(STW 极短): 标记 GC Roots 直接关联的 Region。
    2. 并发标记: 与用户线程并发标记存活对象。
    3. 最终标记(STW): 处理并发阶段的增量更新(使用 SATB 快照算法)。
    4. 筛选回收(STW): 根据停顿时间目标选择 Region 进行回收,采用复制算法。

关键说明:
G1 设计上通过 Mixed GC(混合回收) 避免 Full GC,但在极端情况(如并发标记失败)下仍可能触发 Full GC,此时 触发故障回退机制(Fallback Mechanism) 到单线程的 Serial Old收集器(标记-整理算法)

  • 触发条件: 堆内存不足且无法通过 Mixed GC 回收足够空间。
  • 优化建议: 通过 -XX:G1ReservePercent 增加预留内存,降低 Full GC 概率。

适用场景:

大堆内存(数十GB至数百GB)、追求低延迟与吞吐量平衡的应用。

参数:

# 启用G1收集器(JDK 9+默认)
-XX:+UseG1GC -Xmx16g

# 设置最大停顿时间目标(毫秒)
-XX:MaxGCPauseMillis=200

# 设置Java堆区域大小(默认2的幂,1-32MB)
-XX:G1HeapRegionSize=8m

# 老年代占用50%时启动混合回收
-XX:InitiatingHeapOccupancyPercent=50
#  G1 垃圾收集器预留堆内存的百分比,默认值:10(即预留 10% 的堆内存)
-XX:G1ReservePercent=10

4.5 ZGC收集器(Z Garbage Collector)(超低延迟,JDK 11+)
开始ZGC
初始标记(极短STW)
并发标记
再标记(极短STW)
并发转移准备
并发转移(通过读屏障修正引用)
完成GC,几乎无STW

特点:

  • 着色指针(Colored Pointers)
    将 GC 信息(Marked0/1、Remapped)存储在指针的低 4 位,无需扫描对象头。

  • 读屏障(Load Barrier)
    在读取对象引用时动态修正指针,实现并发移动。

  • 并发整理
    全程几乎无 STW(仅初始标记和再标记有极短停顿,通常 < 1ms)。

适用场景:

超大堆内存(TB 级),追求极致低延迟(<10ms),如金融交易系统。

算法:

  • 并发标记-整理算法(Concurrent Mark-Compact)

    1. 并发标记: 利用染色指针(Colored Pointers)标记对象状态。

    2. 并发预备重分配: 确定需要清理的内存区域。

    3. 并发重分配: 将存活对象复制到新地址,并更新引用。

    4. 并发重映射: 修复旧地址到新地址的映射。

    5. 关键技术: 染色指针和读屏障,支持全阶段并发执行。

参数:

# 启用ZGC(需JDK 11+)
-XX:+UseZGC -Xmx32g 

# 设置最大堆大小(ZGC适合大内存)
-Xmx64g -Xms64g

# 设置并发GC线程数(默认CPU核心数的1/4)
-XX:ConcGCThreads=4

# 配置GC周期的最大停顿时间(目标值)
-XX:MaxGCPauseMillis=10

4.6 Shenandoah收集器(超低延迟,OpenJDK 12+)
触发GC
初始标记(STW)
并发标记
最终标记(STW)
并发回收准备
并发对象转移
引用修正(读屏障)
并发清理
完成GC,恢复应用线程

特点:

  • 与 ZGC 类似,基于转发指针和布鲁姆过滤器实现并发回收,停顿时间极短。

适用场景:

OpenJDK 环境下的超大堆内存(TB级)、极低停顿时间(亚毫秒级)、低延迟应用。

算法:

  • 并发复制算法(Concurrent Copying)

    1. 并发标记: 与用户线程并发标记存活对象。

    2. 并发预清理: 确定需要回收的Region。

    3. 并发转移: 在用户线程运行时,将存活对象复制到新Region。

    4. 引用更新: 并发更新对象引用至新地址。

    5. 关键技术: 使用 读屏障(Read Barrier) 实现并发对象移动。

参数:

# 启用Shenandoah(需JDK 12+,OpenJDK默认包含)
-XX:+UseShenandoahGC -Xmx16g 

# 设置GC策略(normal/size/reference-time)
-XX:ShenandoahGCHeuristics=normal

# 设置最大停顿时间目标(毫秒)
-XX:MaxGCPauseMillis=100

III、垃圾收集器对比表

收集器 新生代算法 老年代算法 Full GC 收集器 JDK版本支持 默认收集器(JDK版本) 推荐堆大小 吞吐量 适用场景 是否开源 所属JVM实现 垃圾对象判定算法 核心特点
Serial 复制 标记-整理 Serial Old(标记-整理) JDK 1.0+ JDK 1.0~8(客户端模式) <512MB 客户端应用、嵌入式系统 HotSpot 可达性分析(迭代标记) 单线程简单高效,无多线程开销
Parallel 复制 标记-整理 Parallel Old(标记-整理) JDK 1.4+ JDK 8(服务器模式) 512MB~32GB 批处理、计算密集型任务 HotSpot 可达性分析(并行标记) 多线程并行回收,最大化吞吐量
CMS 复制(ParNew) 标记-清除 Serial Old(标记-整理) JDK 1.5~JDK14 (JDK 9+ 标记为废弃,JDK 14+ 移除) 无(需手动启用) 512MB~4GB 低延迟Web服务(中小堆) HotSpot 可达性分析(三色标记 + 增量更新) 低延迟,并发标记减少停顿,但需监控碎片问题
G1 复制 标记-整理(分Region) Serial Old(标记-整理) JDK 7u4+ JDK 9+(服务器模式) 4GB~64GB 中~高 大堆内存、平衡型应用 HotSpot 可达性分析(三色标记 + SATB快照) 分区回收,可预测停顿,自动整理碎片
ZGC 并发复制 并发标记-整理 无(无 Full GC 概念) OpenJDK 11+ JDK 15+(可选) ≥4GB 超大堆、亚毫秒级停顿 HotSpot 可达性分析(并发三色标记 + 染色指针) 超大堆,染色指针,停顿<1ms
Shenandoah 并发复制 并发复制 无(无 Full GC 概念) OpenJDK 12+ 无(需手动启用) ≥4GB 超大堆、极低延迟需求 HotSpot 可达性分析(并发三色标记 + 读屏障) 全堆并发,停顿<10ms

IIII、垃圾对象判定算法详解

收集器 判定算法实现
Serial 递归遍历GC Roots,标记可达对象(无并发优化)。
Parallel 多线程并行遍历GC Roots,标记可达对象。
CMS 三色标记法 + 增量更新(将修改引用的黑色对象重新标记为灰色)。
G1 三色标记法 + SATB(原始快照)(基于标记开始时的引用关系进行标记)。
ZGC 并发三色标记法 + 染色指针(通过指针元数据记录对象状态)。
Shenandoah 并发三色标记法 + 读屏障(拦截对象访问,处理并发标记中的引用变化)。

五、GC 日志分析与可视化工具

获取GC日志
分析GC类型(Minor/Full)
提取停顿时间
计算吞吐量
分析内存使用趋势
定位频繁GC原因
确定是否存在内存泄漏
调整堆大小/收集器参数
验证优化效果

5.1 典型 GC 日志示例

[GC (Allocation Failure) [PSYoungGen: 524288K->1024K(524288K)] 524288K->1025K(1048576K), 0.0012345 secs]

解析:

  • GC:新生代 GC。
  • Full GC:整堆 GC。
  • Allocation Failure:因内存分配失败触发 GC。
  • PSYoungGen:Parallel Scavenge 收集器的新生代。
  • 524288K->1024K:回收前→回收后的内存使用量。
  • 0.0012345 secs:GC 耗时。

5.2 可视化工具推荐

  • GCViewer:解析 GC 日志,生成可视化图表(如停顿时间、内存使用趋势)。
  • GCEasy:在线工具,上传 GC 日志后提供详细分析报告和优化建议。
  • JProfiler:集成 GC 分析功能,支持实时监控和堆转储分析。
  • Java Mission Control (JMC):Oracle 官方工具,提供高性能监控和诊断。
  • Arthas(阿尔萨斯):阿里巴巴开源的 实时诊断工具,通过命令行或 Web UI 动态监控和排查问题。
  • VisualVM:轻量级可视化工具,集成多种性能分析功能(含 GC 监控),支持插件扩展。

六、性能调优实战案例

6.1 高并发 Web 应用调优

场景: 8 核 16GB 服务器,Tomcat 应用,响应时间敏感。
配置:

java -Xms8g -Xmx8g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-XX:G1HeapRegionSize=8m \
-XX:+ParallelRefProcEnabled \
-jar app.jar

效果:

  • 新生代 GC 频率降低,停顿时间控制在 50ms 以内。
  • 老年代碎片化问题缓解,Full GC 频率从每天数次降为每周一次。

6.2 大数据批处理调优

场景: Spark 作业,64GB 内存,追求高吞吐量。
配置:

spark-submit --conf spark.executor.memory=48g \
--conf spark.executor.extraJavaOptions="-XX:+UseParallelGC -XX:GCTimeRatio=99" \
--class com.example.BatchJob app.jar

效果:

  • 吞吐量提升 20%,GC 时间占比从 5% 降至 1%。
  • 并行 GC 充分利用多核 CPU,减少 STW 时间。

七、总结与最佳实践

垃圾判定:优先使用可达性分析,避免引用计数法的循环引用问题。
算法选择

  • 新生代:对象存活率低→复制算法。
  • 老年代:对象存活率高→标记 - 整理算法。

收集器选型

<=4GB
4GB-16GB
高吞吐量
低延迟
>16GB
高吞吐量
低延迟
选择垃圾收集器
堆内存大小
Serial/Parallel
延迟要求
Parallel+Parallel Old
CMS/G1
延迟要求
Parallel+Parallel Old
是否需要商业支持
ZGC
Shenandoah
CMS(老年代)+ ParNew(新生代)
G1
适用于老年代对象存活率高、允许一定碎片的场景
适用于大内存、追求平衡的场景
  • 小内存 / 单线程:Serial。
  • 吞吐量优先:Parallel。
  • 低延迟:CMS(老年代)、G1(大内存)、ZGC/Shenandoah(超大内存)。

性能监控:

  • 定期分析 GC 日志,监控停顿时间和内存使用趋势。
  • 使用可视化工具定位内存泄漏和大对象问题。

通过结合图解和实战案例,可更直观地理解 JVM 垃圾回收机制的底层原理,从而针对性地优化应用性能。

附:

  • JVM图解
本地方法接口
执行引擎
Java虚拟机运行时数据区
类加载子系统
垃圾回收器
方法区(线程共享)
堆内存(线程共享)
虚拟机栈(线程私有)
程序计数器(线程私有)
链接(Linking)
启动类加载器
扩展类加载器
系统类加载器
对象分配
对象分配
晋升
晋升
本地方法库
JNI
操作系统资源
逐行解释字节码
解释器
中间代码生成器
即时编译器
代码优化器
目标代码生成器
新生代GC
GC管理器
老年代GC
垃圾回收器分类
Serial
新生代收集器
ParNew
Parallel Scavenge
老年代收集器
Serial Old
Parallel Old
CMS
混合收集器
G1
ZGC
Shenandoah
本地方法存储
本地方法栈(线程私有)
线程A计数器
线程B计数器
栈帧
线程A
局部变量表
操作数栈
动态链接
方法返回地址
附加信息
栈帧
线程B
Eden
新生代
Survivor0
Survivor1
老年代
类元信息
方法元信息
运行时常量池
静态变量
加载
.class文件
初始化
准备
验证
解析
  • ⚙️运行时数据区的初始化时机
运行时数据区 初始化时机 与类加载的关系
方法区 JVM 启动时分配内存,类加载时写入类元信息、常量池、静态变量等。 类加载的初始化阶段会向方法区写入静态变量。
JVM 启动时分配内存,对象实例在 new 时创建。 类初始化完成后,通过 new 触发对象分配。
虚拟机栈 线程创建时初始化,每个线程有独立栈空间,栈帧在方法调用时动态生成。 与类加载无关,由线程执行引擎管理。
程序计数器 线程创建时初始化,记录当前执行指令地址。 与类加载无关,由线程执行引擎管理。
本地方法栈 线程创建时初始化,存储本地方法(Native Method)调用信息。 与类加载无关,由本地方法接口(JNI)管理。
  • 类加载与运行时数据区的关系
运行时数据区
类加载子系统
写入
触发对象创建
方法调用时创建栈帧
虚拟机栈
本地方法栈
程序计数器
初始化
链接
加载
方法区
栈帧
局部变量表
操作数栈