G1垃圾回收器
G1 垃圾回收器简介
G1(Garbage First)垃圾回收器是Java HotSpot虚拟机中的一种高性能垃圾回收器,设计用于处理大规模内存管理需求,特别是对低延迟和高吞吐量有严格要求的应用程序。G1 垃圾回收器能够在提供良好响应时间的同时,确保高效的内存管理。
Garbage First 收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。早在 JDK7 刚刚确立项目目标、Oracle公司制定的 JDK7 RoadMap 里面,G1 收集器就被视作 JDK7 中HotSpot 虚拟机的一项重要进化特征。从 JDK6 Update 14 开始就有 Early Access 版本的 G1 收集器供开发人员实验和试用,直至 JDK7 Update 4,Oracle才认为它达到足够成熟的商用程度,移除了 “Experimental” 的标识; 到了 JDK8 Update 40的时候,G1 提供并发的类卸载的支持,补全了其计划功能的最后一块拼图。这个版本以后的 G1 收集器才被 Oracle 官方称为 “全功能的垃圾收集器”(Fully-Featured Garbage Collector)。
从 JDK9 开始 G1 一直是默认的垃圾回收器。
设计思想
在G1收集器出现之前的所有其他收集器,包括 CMS 在内,垃圾收集的目标范围要么是整个新生代(Minor GC)
,要么就是整个老年代(Major GC)
,再要么就是整个 Java堆(Full GC)
。而 G1 跳出了这个樊笼,它可以面向堆内存任何部分来组成回收集 (Collection Set,一般简称CSet) 进行回收,衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的 Mixed GC 模式。
1. 设计目标
- 低延迟:旨在减少垃圾回收对应用程序的停顿时间。
- 高吞吐量:尽量减少应用程序的中断,从而提高整体吞吐量。
- 可预测性:提供可配置的暂停时间目标,便于预测应用程序性能。
2. 适用场景
- 大型堆内存:适合多GB甚至数十GB的大堆内存。
- 低延迟要求:特别适用于需要低延迟和可预测的应用程序,例如实时系统、在线服务等。
- 动态对象分配:适合频繁对象创建和销毁的场景。
3. 基本工作原理
G1 垃圾回收器将堆划分为多个大小相等的区域(region),每个区域可以包含不同类型的对象:新生代(Young Generation)和老年代(Old Generation)对象。
G1 根据区域的垃圾比例,优先回收垃圾最多的区域,从而优化内存回收的效率。
G1 从整体来看是基于 “标记-整理” 算法实现的收集器,但从局部(两个 Region 之间)上看又是基于 “标记-复制” 算法实现,无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,垃圾收集完成之后能提供规整的可用内存。这种特性有利于程序长时间运行,在程序为大对象分配内存时不容易因无法找到连续内存空间而提前触发下一次收集。
G1 垃圾回收器的主要特点
1. 区域划分
- 区域大小:堆被划分成若干大小相等的区域,每个区域大小通常在1MB到32MB之间。
- 多种区域类型:包括 Eden 区(新生代)、Survivor 区(存活区)、Old 区(老年代)、Humongous 区(超大对象区)。
虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异, G1 不再坚持固定大小以及固定数量的分代区域划分,而是把连续的 Java 堆划分为多个大小相等的独立区域 (Region),每一个 Region 都可以根据需要,扮演新生代的 Eden 空间、Survivor 空间,或者老年代空间。收集器能够对扮演不同角色的 Region采 用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
2. 增量压缩
- 混合回收:G1 通过增量式的方式进行垃圾回收,即在一次回收过程中,不仅回收新生代,还会回收部分老年代。
- 增量整理:G1 垃圾回收器在进行垃圾回收时,会对部分区域进行整理,释放出连续的可用内存空间。
3. 并行与并发回收
- 并行回收:G1 垃圾回收器可以使用多核 CPU 来并行执行垃圾回收任务,从而减少回收时间。
- 并发回收:在垃圾回收期间,应用程序可以继续执行,从而减少了应用程序停顿时间。
4. 可预测的停顿时间
- 停顿时间控制:G1 允许用户设定最大停顿时间目标(例如,200毫秒),以便根据应用程序需求进行调优。
- 停顿时间预测:G1 会根据历史垃圾回收数据,动态调整回收行为,以尽量满足设定的停顿时间目标。
5. 分阶段回收
- 初始标记:快速标记活跃对象的根集。
- 并发标记:并发标记所有活动对象,以便识别需要回收的对象。
- 最终标记:完成对活跃对象的标记,并重新标记从根到活动对象的路径。
- 回收清理:对被标记为垃圾的区域进行回收和整理。
G1 的工作流程
- 初始标记(Initial Marking): 仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS 指针的值,让下一阶段用户线程并发运行时,能正确地在可用的 Region 中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行 Minor GC 的时候同步完成的,所以 G1 收集器在这个阶段实际 并没有额外的停顿。
- 并发标记(Concurrent Marking): 从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理 SATB 记录下的在并发时有引用变动的对象。
- 最终标记(Final Marking): 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录。
- 筛选回收(Live Data Counting and Evacuation): 负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个 Region 构成回收集,然后把决定回收的那一部分 Region 的存活对象复制到空的 Region 中,再清理掉整个旧 Region 的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
G1 收集器除了并发标记外,其余阶段也是要完全暂停用户线程的,它并非纯粹地追求低延迟,官方给它设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才能担当起 “全功能收集器” 的重任与期望。
其实本也有想过设计成与用户程序一起并发执行,但这件事情做起来比较复杂,考虑到 G1 只是回收一部分 Region,停顿时间是用户可控制的,所以并不迫切去实现,而选择把这个特性放到了 G1 之后出现的低延迟垃圾收集器 ZGC 中。还考虑到 G1 不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保证吞吐量所以才选择了完全暂停用户线程的实现方案。
可以通过 MaxGCPauseMillis 参数设置服务为吞吐量优先还是响应时长优先。
和 CMS 的回收四个步骤倒是挺像的。
G1 垃圾回收器的优势
- 低停顿时间:通过增量回收和并发执行,减少应用程序的停顿时间。
- 高可预测性:提供可配置的停顿时间目标,便于调优和预测性能。
- 适合大堆内存:能够高效管理多GB甚至数十GB的堆内存。
- 灵活性:支持多种不同类型的对象分配和回收策略,适应各种应用场景。
G1 垃圾回收器的调优
1. 设定停顿时间目标
- 使用
-XX:MaxGCPauseMillis=<N>
来设定最大停顿时间,例如:-XX:MaxGCPauseMillis=200
2. 调整并发线程
- 使用
-XX:ParallelGCThreads=<N>
和-XX:ConcGCThreads=<N>
来调整并发和并行线程的数量。
3. 监控和分析
- 使用
-XX:+PrintGCDetails
和-XX:+PrintGCApplicationStoppedTime
等选项监控垃圾回收过程,以便进行分析和调优。
示例配置
以下是一个典型的 G1 垃圾回收器配置示例:
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 -XX:G1ReservePercent=10
-XX:+UseG1GC
:启用 G1 垃圾回收器。-XX:MaxGCPauseMillis=200
:设置最大 GC 停顿时间为 200 毫秒。-XX:InitiatingHeapOccupancyPercent=45
:设置堆使用率达到 45% 时触发混合垃圾回收。-XX:G1ReservePercent=10
:设置保留内存百分比,用于临时分配和减少频繁的 Full GC。
总结
G1 垃圾回收器通过灵活的内存管理和高效的垃圾回收机制,为 Java 应用程序提供了低停顿时间和高吞吐量的性能保证。其设计适用于大型堆内存管理和低延迟应用场景。