目录
2.2、分区Region的Collect Set(CSet)
1、G1背景
为了取代 CMS 收集器,CMS有以下缺点:
- CMS收集器对CPU资源非常敏感,会占用CPU资源而导致引用程序变慢;
- CMS收集器无法处理浮动碎片垃圾,不能像其他收集器那样等到老年代几乎完全被填满了再进行收集;
G1优点:
- 基于标记-整理算法,不产生碎片化问题;
- 可以控制停顿时间,提升吞吐量,JDK1.8开始默认G1
G1是标记-清除还是标记-整理算法:
- G1采用了标记-整理和标记-清除两种方式的结合;
- 标记-清除发生在并发标记阶段,而标记-整理主要发生在混合收集阶段
2、G1内存模型
2.1、分区Region
G1 收集器不采用传统的新生代和老年代物理隔离方式收集,采用的是逻辑上划分新生代和老年代。将整个堆内存划分为2048个大小相等的独立内存块Region,大小是2的N次方大概每一块在 1M - 32M 之间,使用不同的Region来表示新生代和老年代,再要求相同类型的 Region 在物理内存上相邻所以是逻辑上划分。每个 Region 分区只能是一种角色,Eden区、S区、老年代O区、H巨型对象区。
什么是H巨型对象区:大小超过一个Region容量的50%以上的对象存放的区域,为了解决如果它是一个短期存活的巨型对象,放入老年代就会对垃圾收集器造成负面影响,触发老年代频繁GC,所以引入H区概念。如果一个H区装不下巨型对象,G1会寻找连续的H分区来存储,如果寻找不到连续的H区,就启动 Full GC 全局回收。
2.2、分区Region的RSet
RSet是一个反向指针( HashTable 的集合),记录了其它 Region 对当前 Region 的引用情况。回收某个Region时,不需要执行全堆扫描(串行和并行收集器就需要),只需扫描它的 RSet 就可以找到外部引用,来确定引用本Region分区内的对象是否存活。本Region的引用不用记录、新生代的引用也不用记录(因为GC会全部扫描新生代),所以只需要记录新生代和老年代之间的引用。
哈希表(HashTable )是实现 RSet 的一种常见方式。一个Region可能有多个线程在并发修改,因此也可能会并发修改 RSet。为避免冲突,G1垃圾回收器进一步把 RSet 划分成了多个 HashTable,每个线程都在各自的 HashTable 里修改,从逻辑上来说,RSet 就是这些 HashTable 的集合。
RSet 的写屏障:每次将一个老年代对象的引用修改为指向年轻代对象,都会被写屏障捕获并记录下来,因此在年轻代回收的时候,就可以避免扫描整个老年代来查找根
2.3、分区Region的Card卡片
一个 Card Table 将一个 Region 在逻辑上划分为若干个固定大小(介于128到512字节之间)的连续区域,每个区域称之为卡片 Card,因此 Card 是堆内存中的最小可用粒度,分配的对象会占用物理上连续的若干个卡片。
作用:如果一个线程修改了Region内部的引用,就必须要去通知RSet,更改其中的记录,引用的对象很多,赋值器需要对每个引用做处理,开销很大,引入卡片可解决。查找引用是可以通过卡片查找,G1对内存的使用以分区(Region)为单位,而对对象的分配则以卡片(Card)为单位。
2.2、分区Region的Collect Set(CSet)
CSet是G1垃待回收的Region集合,用于追踪存活对象,找到所有存活对象并将他们复制到空闲区域中。CSet 所有分区都会被释放,内部存活的对象都会被转移到分配的空闲分区中。
3、G1垃圾收集流程
G1提供2种GC模式,分别是Young GC和Mixed GC,Full GC是大家都有的
3.1、对象分配策略
一个Region 分为2个部分,已分配和未分配,界限叫做top,所以分配对象就是增加top的值的过程
(1)TLab中分配(线程本地分配缓冲区,ThreadLocal+CAS实现)
如果对象在共享空间中分配就要用ThreadLocal同步机制解决并发冲突问题,对象分配时在这个buff分配线程之间不用进行同步。但是线程消耗完自己buff后依然要申请新的buff也会带来并发问题,这里用CAS解决。对象过大会申请新的buff原来的有空余会浪费掉,带来碎片化问题。
(2)Eden区分配
TLab无法分配的对象才在Eden区分配,如果Eden也无法分配只能在老年代分配了
(3)H区分配
巨型对象在这里分配,无法享受Tlab带来的优化,要避免生成巨型对象。
3.2、Young GC
(1)标记阶段:根扫描
根是只statis指向的对象、变量等,根引用+RSet记录的外部引用作为扫描存活对象的入口。
(2)清理阶段:对Eden区和S区垃圾回收,年轻代中没有RSet(记录了老年代到年轻代的引用)引用的会被回收
(3)对象(内存)拷贝
清理阶段完成后,GC将存活对象从Eden区和From S区拷贝到To S区,并清空Eden区和From S区,同时采用复制算法,GC将To S区和From S区角色互换。
3.3、Mixed GC
年轻代不断进行垃圾回收活动后,为了避免老年代的空间被耗尽(会往老年代送),老年代占用空间达到默认45%时会触发一次混合垃圾回收Mixed GC,对新生代和老年代一起回收。
(需要分配老年代的对象时,但发现没有足够的空间,这个时候就会触发一次 Full GC)
Mixed GC分2步骤:并发标记、拷贝存活对象。
(1)全局并发标记
- 初始标记:标记根节点以及直接可达的对象;
- 根区域扫描:描初始标记的存活区中( s区)可直达的老年代区域的对象,(因为RSet只记录只被年轻代引用的老年代,在一次Young GC中这些年轻代会被复制到S区);
- 并发标记:采用三色标记算法, GC Roots 对堆中的对象进行可达性分析,找出存活的对象标记。
- 重新标记:标记并发标记遗留的对象
(2)拷贝存活对象
3.4、Full GC
当 G1 无法在堆空间中申请新的分区时,G1便会触发担保机制,执行一次STW式的、单线程的 Full GC,Full GC会对整堆做标记清除和压缩,最后将只包含纯粹的存活对象.
G1在以下场景中会触发 Full GC:
- 从年轻代分区拷贝存活对象时,无法找到可用的空闲分区;
- 老年代分区转移存活对象时,无法找到可用的空闲分区;
- 分配巨型对象时在老年代无法找到足够的连续分区;
4、三色标记算法
属于并发收集阶段
- 黑色:根对象,或者该对象与它的子对象都被扫描了
- 灰色:对象本身被扫描,但还没扫描完该对象中的子对象
- 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象
5、G1 收集器的缺点
(1)如果停顿时间过短的话,可能导致每次选出的回收集只占堆内存很小一部分,收集器收集的速度逐渐跟不上分配器的分配速度,进而导致垃圾慢慢堆积,最终造成堆空间占满,引发Full GC 反而降低性能。
(2)G1 内存占用、执行负载都要比CMS要高
(3)小内存的情况下使用CMS收集器,大内存的情况下可以使用G1收集器(G1收集器6GB以上)