1. JVM垃圾回收机制深度解析
在Java开发中,垃圾回收(GC)机制是JVM最核心的功能之一。作为Java开发者,深入理解GC原理和调优方法对于编写高性能应用至关重要。本文将系统性地介绍JVM垃圾回收的核心概念、算法实现和调优实践。
1.1 垃圾回收基础概念
垃圾回收的本质是自动管理堆内存中不再使用的对象。与C/C++等需要手动管理内存的语言不同,Java通过GC自动回收无用对象,极大降低了内存泄漏的风险。但这也带来了额外的性能开销,特别是在不合理的GC配置下,可能导致应用出现明显的卡顿。
GC的核心工作流程分为三个步骤:
- 标记(Mark):识别哪些对象还在被使用
- 清除(Sweep):回收未被标记的对象占用的内存
- 整理(Compact):可选步骤,整理内存碎片
1.2 对象存活判断算法
JVM使用可达性分析算法来判断对象是否存活。这个算法的核心思想是:从一组称为"GC Roots"的根对象出发,通过引用链遍历所有可达对象,未被遍历到的对象即为可回收的垃圾。
GC Roots通常包括:
- 虚拟机栈中引用的对象(局部变量、参数等)
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
与引用计数法相比,可达性分析能有效解决循环引用问题,这也是现代JVM普遍采用此算法的原因。
提示:在进行可达性分析时,JVM需要暂停所有应用线程(STW),以确保对象引用关系不被修改。这也是GC会产生停顿的根本原因。
1.3 垃圾回收算法详解
1.3.1 标记-清除算法
标记-清除是最基础的GC算法,分为两个阶段:
- 标记阶段:标记所有需要回收的对象
- 清除阶段:统一回收被标记的对象
优点:
- 实现简单
- 不需要移动对象
缺点:
- 会产生内存碎片
- 效率较低
适用场景:老年代回收(CMS收集器采用此算法的变种)
1.3.2 标记-复制算法
标记-复制算法将内存分为两块,每次只使用其中一块。当这块内存用尽时,将存活对象复制到另一块,然后清除已使用的内存。
优点:
- 解决内存碎片问题
- 分配内存高效(只需移动指针)
缺点:
- 内存利用率只有50%
- 复制大量存活对象时开销大
适用场景:新生代回收(大多数收集器的新生代都采用此算法)
1.3.3 标记-整理算法
标记-整理算法在标记完成后,将所有存活对象向内存一端移动,然后清理边界外的内存。
优点:
- 无内存碎片
- 内存利用率高
缺点:
- 移动对象成本高
- 需要STW时间较长
适用场景:老年代回收(Parallel Old、G1等收集器采用)
1.3.4 分代收集算法
现代JVM普遍采用分代收集策略,将堆内存划分为新生代和老年代,针对不同代采用最适合的算法:
- 新生代:对象生命周期短,适合标记-复制算法
- 老年代:对象存活率高,适合标记-清除或标记-整理算法
分代依据是"弱代假说":绝大多数对象都是朝生夕死的,存活时间长的对象通常会继续存活很长时间。
1.4 垃圾收集器详解
1.4.1 Serial收集器
Serial是最基础的收集器,单线程工作,进行GC时必须暂停所有应用线程(Stop The World)。
特点:
- 简单高效
- 适合单核CPU或小内存应用
- JDK客户端模式默认收集器
1.4.2 Parallel Scavenge收集器
Parallel Scavenge是新生代收集器,采用多线程并行GC,关注吞吐量优化。
特点:
- 多线程并行收集
- 适合后台计算型应用
- 可配置最大GC停顿时间或吞吐量目标
1.4.3 CMS收集器
CMS(Concurrent Mark Sweep)是以获取最短回收停顿时间为目标的收集器,采用标记-清除算法。
工作流程:
- 初始标记(STW):标记GC Ro
