垃圾收集器--G1垃圾收集器(全区域的垃圾收集器)
垃圾收集器–G1垃圾收集器(全区域的垃圾收集器)
文章目录
- 垃圾收集器—G1垃圾收集器(全区域的垃圾收集器)
- 概述
- G1垃圾收集器
- 年轻代垃圾收集
- 老年代垃圾收集
- 巨型对象
- 跨代引用问题
概述
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷.
G1垃圾收集器
用在堆内存很大的情况下,把heap划分为很多很多的region块,然后并行的对其进行垃圾回收。
G1垃圾回收器回收region的时候基本不会STW,而是基于 most garbage优先回收(整体来看是基于”标记-整理“算法,从局部即两个region之间基于”复制”算法)的策略来对region进行垃圾回收的。
分区:
G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。
图中的E表示该region属于Eden内存区域,S表示属于Survivor内存区域,T表示属于Tenured(老年代)内存区域。图中空白的表示未使用的内存空间。G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。这种内存区域主要用于存储大对象,即大小超过一个region大小的50%的对象。
年轻代垃圾收集
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法。把Eden区和Survivor区的对象复制到新的Survivor区域。在两个region区域之间用的是复制算法。
注意:在年轻代进行垃圾回收时,就会进行初始标记。
老年代垃圾收集
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同:
初始标记(Initial Mark)阶段 同CMS垃圾收集器的Initial Mark阶段一样,G1也需要暂停应用程序的执行,它会标记从根对象出发,在根对象的第一层孩子节点中标记所有可达的对象。但是G1的垃圾收集器的初始标记阶段是跟minor gc一同发生的。也就是说,在G1中,你不用像在CMS那样,单独暂停应用程序的执行来运行Initial Mark阶段,而是在G1触发minor gc的时候一并将年老代上的初始标记给做了。
并发标记(Concurrent Mark)阶段 在这个阶段G1做的事情跟CMS一样。但G1同时还多做了一件事情,就是如果在并发标记阶段中,发现哪些老年代中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段。这也是Garbage First名字的由来。同时,在该阶段,G1会计算每个 region的对象存活率,方便后面的clean up阶段使用 。
注意:主要是因为在并发标记期间,垃圾对象被非垃圾对象重新引用,然后会把垃圾对象进行写屏障,然后将该垃圾对象放入队列中,然后在重新标记阶段,会将队列中的垃圾进行重新判断,如果是非垃圾对象,就会进行重新标记为非垃圾对象。
最终标记(CMS中的Remark阶段) 在这个阶段G1做的事情跟CMS一样, 但是采用的算法不同,G1采用一种叫做SATB(snapshot-at-the-begining)的算法能够在Remark阶段更快的标记可达对象。
SATB全称snapshot-at-the-beginning,由Taiichi Yuasa为增量式标记清除垃圾收集器开发的一个算法,主要应用于垃圾收集的并发标记阶段,解决了CMS垃圾收集器重新标记阶段长时间STW的潜在风险。
筛选回收(Clean up/Copy)阶段 - 在G1中, 它有一个Clean up/Copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的,如下图所示:
巨型对象
巨型对象 Humongous Region
一个大小达到甚至超过分区大小一半的对象称为巨型对象(Humongous Object)。当线程为巨型分配空间时,因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)。G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。
巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型(StartsHumongous),相邻连续分区被标记为连续巨型(ContinuesHumongous)。并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。
跨代引用问题
主要就是老年代对象引用新生代的问题。
就会用卡表来解决,如果老年代对象引用新生代对象时,就会将该老年代对象进行标记为脏卡,进行新生代回收时,主要是缩小了查找范围,直需要从脏卡的范围内进行查找,提高了效率。并不会立即将该对象标记为脏卡,而是将该对象引用放入引用队列中,用线程来实现。
还没有评论,来说两句吧...