[JVM] G1回收器(二)

G1回收器(二)

时间停顿模型:能够在一个指定长度为M毫秒的时间段内,消耗在垃圾收集上的时间大概率不超过N毫秒

该模型就是G1回收器需要实现的目标

在G1之前,所有的垃圾回收器的回收目标都是一整个分代,如整个新生代,整个老年代,或者整个Java堆,G1则不同,它不再面向单个分代来进行垃圾回收,而是使用回收集(Collection Set),回收集的组成可以是任意的堆内存,衡量回收集的标准是,哪块内存存放垃圾最多,回收收益最大,这个就是G1独有的Mixed GC模式

G1中同样存在新生代、老年代的划分,但是G1没有使用之前的堆内存分布,而是将整个堆划分为了多个大小相等的Region,每个Region都可以根据需要,被划分给老年代或者新生代

G1中的大对象不再属于老年代了,而是提供了Humongous区域专门存放大对象,对大对象的判断也与之前不同了,之前是通过JVM参数-XX:PretenureSizeThreshold来控制,而G1中定义如果一个对象超过了单个Region的50%的内存,则这个对象就是大对象。每个Region的大小必须为2的N次幂,也可以通过参数-XX:G1HeapRegionSize,一个大对象可以横跨多个Region

G1每次回收都是以Region为最小单位的,它会跟踪每个Region的垃圾回收的价值大小,价值就是回收可以获得的空间大小和回收所消耗的时间,然后G1会在后台维护一个优先级列表,每次根据用户设置的允许停顿的最长时间(通过参数-XX:MaxGCPauseMillis指定,默认为200ms),优先回收那些价值最大的Region

G1如何解决跨Region引用的问题

同样也是使用记忆集,卡表来避免全堆的GC Roots扫描,但是和之前在实现上不同

G1中的每个Region都会维护一个自己的记忆集,这些记忆集会记录下其他Region指向自己的指针,并标记这些指针分别在哪些卡页范围内,其结构为一个哈希表,Key为其他Region的起始地址,Value为一个集合,集合中包含卡表的索引号,该结构存储了谁指向我(Key)和我指向谁(Value)

每个Region都需要维护一个记忆集,所以G1比其他收集器所占用额外内存要多大约10%到20%

G1如何解决并发标记阶段用户线程与垃圾回收线程相互干扰的情况

首先需要解决用户线程在垃圾回收线程进行可达性分析时改变对象引用的问题。G1采用的是原始快照(SATB)的方式,当引用关系被改变,比如删除了,则记录这次删除,在可达性分析完成后再以改变的节点为根节点重新进行扫描分析

其次要解决新对象分配内存的问题。G1为每个Region都添加了两个指针TAMS(Top at Mark Start),把每个Region中的一部分空间划分出来用于并发回收过程中的对象分配,并发期间所有的新分配的对象地址都要在这两个指针的位置之上,G1默认在这个地址以上的对象是被隐式标记为存活的,不纳入垃圾回收的范围,与CMS中的concurrent mode failure相似,G1中也会出现并发时用户线程新建对象因内存不足而触发完全的基于serial old回收器的单线程Full GC,导致长时间的STW

G1如何建立可靠的停顿预测模型

G1以衰减均值为理论基础建立停顿预测模型,在垃圾回收过程中,G1会记录每个Region的回收耗时,每个Region记忆集中的脏卡的数量等数据,通过分析析得出平均值、标准偏差、置信度等统计信息,通过这些数据,才能够决定哪些Region放入回收集才可以在不超过期望停顿时间的约束下获得最高的效益

G1垃圾回收的步骤

总体分为四步

  • 初始标记:仅标记GC Roots能够直接关联到的对象,并且修改TAMS的值,让下一节点并发时新对象能够在可用的Region中分配,该步骤实际上是和Minor GC共同完成的,因此G1在这个阶段几乎没有停顿

  • 并发标记:从GC Roots开始,扫描对象图,进行可达性分析,找出需要回收的对象,与用户线程并发,在扫描完成后需要处理SATB记录下的在并发期间有改动引用的对象

  • 最终标记:需要短暂的停顿用户线程,该阶段用于处理并发期间留下的SATB记录

  • 筛选回收:更新Region的统计数据,对Region的回收价值和成本进行排序,根据用户指定的期望停顿时间指定回收计划,选择出多个Region来组成回收集,然后将决定回收的Region中的存活对象复制到空的Region中,再清理掉整个Region,该阶段需要暂停用户线程,并行执行

G1使用的回收算法

总体来看,G1是基于标记-整理算法来进行垃圾回收的,但是在局部(两个Region之间)看来它是基于标记-复制算法来进行垃圾回收的,这两种算法都不会产生内存碎片

你可能感兴趣的:(深入理解Java虚拟机)