开始改变第一天 JVM的原理到调优(3)

63 阅读8分钟

JVM垃圾回收机制深度解析:从算法原理到收集器演进

一、垃圾判定:如何识别"无用对象"

1.1 引用计数法(Reference Counting)

基本原理:

Object A = new Object();  // A的引用计数 = 1
Object B = A;             // A的引用计数 = 2
B = null;                 // A的引用计数 = 1
A = null;                 // A的引用计数 = 0 → 可回收

致命缺陷:循环引用

class Node {
    Node next;
}

Node A = new Node();
Node B = new Node();
A.next = B;  // B引用计数 = 2
B.next = A;  // A引用计数 = 2

A = null;    // A引用计数 = 1
B = null;    // B引用计数 = 1
// 循环引用导致内存泄漏!

1.2 可达性分析(Reachability Analysis)

GC Roots对象包括:

  • 虚拟机栈中的本地变量
  • 静态变量(static成员)
  • 常量引用(final static)
  • 本地方法栈中的变量
  • 活跃线程(Thread对象)
  • 类加载器

可达性分析过程:

GC Roots
    ↓
对象A(可达)
    ↓
对象B(可达) → 对象C(可达)
    ↓
对象D(不可达)→ 垃圾对象

二、垃圾回收核心算法

2.1 标记-清除(Mark-Sweep)

执行过程:

  1. 标记阶段:遍历所有对象,标记存活对象
  2. 清除阶段:回收未被标记的对象空间

优缺点分析:

  • ✅ 实现简单,不需要移动对象
  • 内存碎片:产生不连续内存空间
  • 效率问题:标记和清除都比较耗时

内存变化:

标记前: [使用][空闲][使用][使用][空闲][使用]
标记后: [存活][垃圾][存活][存活][垃圾][存活]
清除后: [存活][空闲][存活][存活][空闲][存活] ← 产生碎片

2.2 复制算法(Copying)

执行过程:

  1. 将内存分为大小相等的两块(From、To空间)
  2. 只使用From空间分配对象
  3. GC时将存活对象复制到To空间
  4. 清空整个From空间

优缺点分析:

  • 无内存碎片:复制后内存连续
  • 高效:只需遍历存活对象
  • 空间浪费:只能使用一半内存

适用场景: 新生代(对象死亡率高)

2.3 标记-整理(Mark-Compact)

执行过程:

  1. 标记阶段:同标记-清除算法
  2. 整理阶段:所有存活对象向一端移动
  3. 清理边界:清理边界外的内存

优缺点分析:

  • 无内存碎片:内存连续分配
  • 空间利用率高:无需预留备用空间
  • 开销较大:需要移动对象,更新引用

适用场景: 老年代(对象存活率高)

三、分代收集策略

3.1 分代理论基础

对象生命周期特征:

  • 新生代:98%的对象"朝生夕死"
  • 老年代:长期存活的对象
  • 永久代/元空间:类信息、常量等

3.2 分代算法选择

堆内存结构:
新生代 (Young Generation) → 复制算法
    ├── Eden区 (80%)
    ├── Survivor0 (10%)
    └── Survivor1 (10%)
    
老年代 (Old Generation) → 标记-整理/标记-清除

对象晋升流程:

  1. 新对象在Eden区分配
  2. Minor GC后存活对象进入Survivor区
  3. 对象年龄计数器+1(每次Minor GC)
  4. 年龄达到阈值(默认15)晋升老年代

四、垃圾收集器演进历程

4.1 新生代收集器

Serial收集器(JDK 1.3)
  • 背景:Java早期,单核CPU为主,应用简单
  • 特点:单线程,Stop-The-World
  • 算法:复制算法
  • 适用:Client模式,内存小的场景
ParNew收集器(JDK 1.4)
  • 背景:多核服务器开始普及
  • 特点:Serial的多线程版本
  • 算法:复制算法
  • 适用:多CPU Server环境
Parallel Scavenge收集器(JDK 1.4)
  • 背景:企业级应用需要更高吞吐量
  • 特点:吞吐量优先
  • 算法:复制算法
  • 参数
    -XX:MaxGCPauseMillis    # 最大GC停顿时间
    -XX:GCTimeRatio         # 吞吐量大小
    

吞吐量计算:

吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
示例:运行100分钟,GC 1分钟 → 吞吐量 = 99/100 = 99%

4.2 老年代收集器

Serial Old收集器(JDK 1.3)
  • 背景:与Serial新生代收集器配套
  • 特点:Serial的老年代版本
  • 算法:标记-整理
Parallel Old收集器(JDK 1.6)
  • 背景:配合Parallel Scavenge,形成吞吐量优先组合
  • 特点:Parallel Scavenge的老年代版本
  • 算法:标记-整理
  • 目标:吞吐量优先

4.3 里程碑:CMS收集器(JDK 1.5)

历史背景:

随着2000年后电商和互联网应用的爆发式增长,用户对系统响应时间的要求越来越高。传统的Serial和Parallel收集器在进行Full GC时,停顿时间可能达到数秒,这在高并发的电商场景中是完全不可接受的。CMS应运而生,专门为解决低延迟问题而设计。

四阶段执行过程:

  1. 初始标记(Stop-The-World):标记GC Roots直接关联对象
  2. 并发标记:GC线程与用户线程并发执行
  3. 重新标记(Stop-The-World):修正并发标记期间的变动
  4. 并发清除:清理垃圾对象

优缺点:

  • 低停顿:大部分工作并发执行,满足电商实时性要求
  • 内存碎片:标记-清除算法导致
  • CPU敏感:并发阶段占用资源

4.4 新一代:G1收集器(JDK 7u4)

历史背景:

随着应用规模不断扩大,堆内存从几GB增长到几十甚至上百GB。CMS在大内存场景下表现不佳:内存碎片问题加剧,并发模式失败风险增加。G1收集器被设计用来替代CMS,在大内存场景下提供更可预测的停顿时间。

革命性改进:

  • 区域化:将堆划分为多个Region(1-32MB)
  • 预测模型:可设置期望停顿时间
  • 混合回收:同时处理新生代和老年代
  • 空间整理:在回收过程中进行碎片整理

五、收集器选择策略

5.1 基于应用场景的选择

年代场景类型推荐收集器历史背景
早期桌面应用/小程序Serial + Serial Old单核CPU,内存有限
中期后台计算系统Parallel Scavenge + Parallel Old批处理任务,追求吞吐量
电商时代Web应用/电商平台ParNew + CMS高并发,低延迟要求
现代大数据/云原生G1 / ZGC大内存,可预测停顿

5.2 停顿时间 vs 吞吐量

停顿时间优先(交互式应用):

# 电商时代的选择
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=68

吞吐量优先(批处理应用):

# 计算密集型任务的选择  
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:GCTimeRatio=99

现代平衡型(大内存应用):

# JDK 8+ 推荐
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

六、历史演进视角与技术驱动

6.1 技术演进时间线

1995-2000:Serial时代
    
2000-2005:Parallel时代(服务器多核化)
      
2005-2012:CMS时代(电商崛起,低延迟需求)
    
2012-现在:G1时代(大数据,云原生)
    
未来:ZGC/Shenandoah(亚毫秒停顿)

6.2 业务需求驱动技术变革

电商场景的特定需求:

  • 双十一/618:瞬时高并发,GC停顿直接影响交易成功率
  • 实时推荐:用户行为分析需要低延迟响应
  • 秒杀系统:毫秒级响应要求GC停顿最小化

技术应对策略:

  1. CMS并发收集:减少STW时间
  2. G1可预测停顿:满足SLA要求
  3. ZGC亚毫秒停顿:支撑金融级应用

6.3 收集器演进的核心驱动力

  1. 硬件发展:单核→多核→NUMA架构
  2. 应用场景:桌面应用→企业应用→互联网→云原生
  3. 业务需求:功能性→性能→用户体验→实时性
  4. 数据规模:MB级→GB级→TB级

七、总结

7.1 核心演进规律

垃圾收集器的演进体现了清晰的业务驱动特征:

  1. 从正确性到性能:早期保证功能正确,后期优化性能指标
  2. 从通用性到场景化:一个收集器适应所有场景→不同场景专用收集器
  3. 从开发者友好到用户友好:减少配置复杂度,提供智能默认值
  4. 从停顿不可控到可预测:满足现代应用对服务质量的严格要求

7.2 技术选型启示

理解垃圾回收器的历史演进,能够帮助我们在技术选型时:

  1. 避免技术惯性:不要因为熟悉就一直使用过时的收集器
  2. 匹配业务特征:根据实际业务场景选择最合适的收集器
  3. 预见技术趋势:了解技术发展方向,提前做好架构准备
  4. 平衡技术债务:在稳定性和先进性之间找到平衡点

垃圾回收技术的发展史,本质上是一部应对不断变化的业务需求和技术挑战的创新史。从Serial到G1的演进,不仅反映了Java技术的成熟,更是整个互联网时代技术变革的缩影。