常用垃圾回收器介绍
Java虚拟机(JVM)提供了多种垃圾收集器,以适应不同的应用场景和性能需求。这些收集器在实现原理、停顿时间、吞吐量和内存占用等方面各具特点。
以下是您列出的几种垃圾收集器的详细介绍及其原理:
Serial收集器
- 原理与特点: Serial收集器是最基本、历史最悠久的垃圾收集器之一。它是一个单线程的收集器,在进行垃圾回收时,必须暂停所有其他用户线程(“Stop-The-World” - STW),直到垃圾回收完成。这种机制简单高效,没有线程切换的开销,因此在单CPU环境或小内存(几十MB到一两百MB)的客户端应用中表现良好.
- 算法: 在新生代,Serial收集器使用标记-复制算法;在老年代,其对应的Serial Old收集器使用标记-整理算法.
- 适用场景: 主要用于单核处理器或客户端应用,对实时性要求不高的场景.
ParNew收集器
- 原理与特点: ParNew收集器可以看作是Serial收集器的多线程版本. 除了使用多条线程进行垃圾收集外,其行为(包括收集算法、Stop-The-World、对象分配规则等)与Serial收集器基本一致. 它同样需要暂停所有用户线程进行垃圾回收. 在多核处理器环境下,ParNew通常比Serial效率更高,但在单核环境下,由于线程切换开销,性能可能反而更差.
- 算法: 在新生代使用标记-复制算法.
- 适用场景: 主要用于新生代的垃圾收集,并且是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,目前唯一能与CMS收集器配合工作的收集器.
Parallel Scavenge收集器
- 原理与特点: Parallel Scavenge收集器也是一个用于新生代的多线程、并行收集器,同样采用复制算法. 它与ParNew最大的不同在于其设计目标是吞吐量优先. 这里的吞吐量指的是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即“运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)”.
- 自适应调节策略: 该收集器提供自适应调节策略 (
-XX:+UseAdaptiveSizePolicy),JVM可以根据当前系统的运行情况,动态调整新生代大小、Eden与Survivor区的比例、晋升老年代对象年龄等参数,以达到最佳的吞吐量或停顿时间目标. - JVM参数:
-XX:MaxGCPauseMillis=N: 设置GC最大停顿时间的目标,收集器会尝试保证内存回收花费的时间不超过设定值。过小的设置可能导致新生代空间变小,GC频率增加,反而降低吞吐量.-XX:GCTimeRatio=N: 设置垃圾收集时间占总时间的比率,即吞吐量的倒数.
- 适用场景: 适用于后台计算,不需要太多交互任务,强调高吞吐量的应用.
CMS (Concurrent Mark Sweep) 收集器
- 原理与特点: CMS收集器是一款以获取最短回收停顿时间为目标的收集器,它第一次实现了让垃圾收集线程与用户线程基本上同时工作,从而降低了GC停顿时间. 它采用标记-清除算法.
- 工作流程: 主要分为四个阶段:
- 初始标记 (Initial Mark): 标记GC Roots直接关联到的对象,需要STW,但耗时短.
- 并发标记 (Concurrent Mark): 从初始标记的对象开始,遍历整个对象图,标记所有可达对象。此阶段与用户线程并发执行,耗时较长但无需STW.
- 重新标记 (Remark): 修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,需要STW,但耗时通常比初始标记阶段长,不过仍远低于并发标记.
- 并发清理 (Concurrent Sweep): 清理已标记为不可达的对象。此阶段与用户线程并发执行,不需STW.
- 缺点:
- 内存碎片: 采用“标记-清除”算法,容易产生大量不连续的内存碎片. 当碎片过多导致无法为大对象分配连续内存时,可能触发一次Full GC,导致长时间的STW,并可能退化为Serial Old收集器作为后备方案.
- 浮动垃圾: 并发清理阶段用户线程仍在运行,可能产生新的垃圾(浮动垃圾),这些垃圾本次无法回收,只能留到下一次GC.
- 对CPU资源敏感: 并发阶段虽然不暂停用户线程,但会占用部分CPU资源,可能导致应用程序整体性能下降.
- JVM参数:
-XX:CMSInitiatingOccupancyFraction=N: 设置CMS收集器触发GC的老年代使用阈值百分比。过低会增加GC频率,过高可能导致并发模式失败(Concurrent Mode Failure),进而触发Full GC.-XX:+UseCMSCompactAtFullCollection: 在Full GC后进行内存碎片整理。虽然可以解决内存碎片问题,但会延长Full GC的停顿时间.
G1 (Garbage-First) 收集器
- 原理与特点: G1收集器是JDK 7引入,并在JDK 9成为默认的垃圾收集器. 它的设计目标是取代CMS,在低延迟和高吞吐量之间寻找平衡. G1引入了分区(Region)概念,将整个Java堆划分为多个大小相等的独立Region块. 每个Region可以扮演新生代的Eden、Survivor或者老年代空间.
- 可预测停顿时间: G1最大的特点是可预测的停顿时间模型. 用户可以通过参数设定期望的GC停顿时间,G1会通过记录每个Region的回收价值(包括回收后的空间大小、回收所需时间等),维护一个优先级列表,优先回收垃圾最多、回收效率最高的Region.
- 算法: 从整体上看,G1基于标记-整理算法实现,可以避免内存碎片;从局部(两个Region之间)上看,则是基于标记-复制算法实现.
- 巨型对象 (Humongous Objects): 对于超过一个Region一半大小的对象,G1会将其视为巨型对象,直接分配到特殊的Humongous Region中,这些Region被视为老年代的一部分.
- 垃圾回收周期:
- Young GC: 主要收集年轻代Region,采用标记-复制算法.
- Mixed GC: 当老年代占用空间超过阈值(
InitiatingHeapOccupancyPercent,默认45%)时,G1会启动一次混合回收周期. Mixed GC会收集整个年轻代以及部分老年代Region. - 并发标记: G1的并发标记过程与CMS类似,包括初始标记(STW)、根区域扫描、并发标记、重新标记(STW)和清理(STW,但此阶段不清理垃圾对象)等阶段. G1使用原始快照(SATB)来解决并发标记时对象引用变化的问题.
- JVM参数:
-XX:+UseG1GC: 启用G1收集器.-XX:MaxGCPauseMillis=N: 设置GC最大停顿时间的目标,G1会尝试达到此目标.
- 优点: 解决了CMS的内存碎片问题,并提供了可预测的停顿时间.
- 缺点: 相比CMS,G1的内存占用和运行时额外负载更高,因为每个Region都需要维护一个记忆集(Remember Set),可能占用堆容量的20%甚至更多内存.
ZGC (Z Garbage Collector)
- 原理与特点: ZGC是JDK 11引入的低延迟垃圾收集器,目标是实现极低的停顿时间(通常不超过10毫秒,甚至可以达到1毫秒以内),且停顿时间不随堆大小(支持8MB到4TB,未来可达16TB)或活跃对象数量的增加而增加. 它几乎不暂停用户线程.
- 核心技术:
- 染色指针 (Colored Pointers): ZGC的关键技术之一。它将对象的状态信息(如是否被标记、是否被重定位)存储在对象指针的高位,而不是对象头中. 这样,GC线程可以直接通过指针颜色判断对象状态,无需额外内存访问. ZGC为每个对象创建了三个虚拟内存地址,通过指针指向不同的虚拟内存地址来表示不同的染色标记.
- 读屏障 (Load Barriers): ZGC的另一个关键技术。每次应用线程加载一个对象引用时,都会触发读屏障. 读屏障会检查指针的颜色,如果对象已被移动,读屏障会确保返回对象的新地址. 通过这种方式,ZGC能够在并发移动对象时保持内存访问的一致性,几乎消除了STW暂停.
- 算法: ZGC采用标记-复制算法,但在标记、转移和重定位阶段几乎都是并发执行的.
- 工作流程: ZGC只有三个需要STW的阶段:初始标记、再标记和初始转移. 这几个阶段的处理时间都与GC Roots的数量成正比,通常耗时非常短. 大部分工作(如并发标记、并发转移和重定位)都在用户线程运行的同时进行.
- 适用场景: 对延迟要求极高,并且可能拥有超大堆内存(数十GB乃至TB级别)的应用场景.
Shenandoah收集器
- 原理与特点: Shenandoah收集器是Red Hat公司开发,于JDK 12中引入的另一款超低延迟垃圾收集器. 其设计目标也是管理大型多核服务器上的超大型堆内存,并将GC暂停时间控制在10毫秒级别,且与堆大小无关. Shenandoah的GC线程可以与应用线程并发执行,从而极大地减少停顿时间.
- 与G1的相似之处: Shenandoah与G1在许多方面相似,例如都使用基于Region的堆内存布局,也有用于存放巨型对象的Humongous Region,并且回收策略上也是优先处理回收价值最大的Region. 它们甚至共享了一部分代码.
- 关键改进:
- 支持并发的整理算法: Shenandoah最重要的改进是实现了并发的整理(回收)算法. 这意味着在GC线程移动对象的同时,用户线程也能并发执行. G1的回收阶段可以并行,但不能与用户线程并发.
- 转发指针 (Brooks Pointers): 为了实现并发整理,Shenandoah使用了转发指针和读屏障. 转发指针类似于Java的句柄定位,但它分散存放在每个对象头前面. 当收集器线程复制对象时,它会更新旧对象头的转发指针指向新副本的地址.
- 引用读写屏障: Shenandoah将读写屏障优化为引用读写屏障,即只对对象引用类型的数据修改加屏障,而不是对所有读写操作都加屏障,从而减少了屏障带来的性能开销.
- 连接矩阵 (Connection Matrix): Shenandoah摒弃了G1中记忆集,改用连接矩阵来记录跨Region的引用关系,降低了记忆集维护的开销.
- 不分代: Shenandoah(目前)默认不使用分代收集,这意味着没有专门的新生代Region或老年代Region.
- 适用场景: 对延迟要求极高的大内存应用场景.