持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
1、G1的引入
CMS使用的是标记清除算法,所以不可避免地会产生内存碎片化的问题,虽然CMS提供了一个-XX:+UseCMSCompactAtFullCollection开关参数,用于CMS不得不进行FullGC时候开启内存碎片的合并整理,但是由于这个内存整理必须移动活动对象,所以无法并发。并且由于它本身的原理,他会产生浮动垃圾,随着内存的扩大,这个缺点会被不断放大,一旦老年代内存分配不下,就会默认启动SO,STW时间直线上升。所以就出现了G1,它吸取了CMS实现方法的教训,因为原本就是因为CMS导致内存碎片化问题,Serial Old处理太大内存导致STW时间延长,那么把内存分为一块块的region,将垃圾比较多的内存采用复制算法,这样效率极大地提升了。
2、原理
region分区
G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆空间划分为多个大小相等的Region,将region作为单次回收的最小单位,即每次收集到的内存空间都是Region大小的整数倍,每个Region都可以根据需要扮演Old、Survivor、Eden、Humongous(大对象区,有可能跨两个甚至跟多的region区,超过单个region50%,一般处理的时候当做老年代处理)。这里体现了分治的思想。
注意:
1、分区和分代矛盾吗?或者说G1分代吗?
首先,G1采取的是逻辑分代,物理不分代的模型。也就是说又分代有分区。G1将分区和分代结合了起来,这两者不矛盾,因为他们本质上都是为了避免一次性扫描较大堆内存空间。只是分代是根据对象的年龄大小进行划分,避免那些不需要扫描的区域进行频繁扫描。而分区是通过控制每次扫描的空间大小来减少每次扫描空间的大小。G1将两者结合了起来,首先,进行了分区,每次只要扫描几个region(由参数-XX:G1GeapRegionSize设定,大小范围1-32MB),避免扫描整个堆。而对象依然可以根据年龄大小划分为老年代和年轻代,所以不同的region扮演老年代,S区,E区和Uhmongous区,收集器能够对扮演不同角色的region采用不同的策略去处理,这样无论是新创建的对象还是已经存活一段时间的对象,还是经过多次收集的老年代对象来说,都能取得很好地回收效果
2、关于新生代和老年代比例
在5%-60%左右,不用手工指定,也不需要手工设定,这是G1预测停顿时间的基准,G1会动态调整。如果YGC本来是200ms,现在变成了400ms,那么下次就把Y区变小,这样动态的调整,以前的GC回收器都是手动调整Y区大小,得把机器停掉才行
3、G1如何实现可预测的停顿模型的?
其他GC都是只使用的分代模型,每次回收的时候都是以整个代为单位进行回收,回收的时间取决于相应代内存的大小以及垃圾的多少,所以停顿时间是不可预测的;而G1不是这样的,首先,也是所有所有的基础,它采用了基于Region的堆内存分布,region是单次回收的最小单位,通过衰减均值理论来决定现在开始回收的话,回收哪些region可以在不超过期望停顿时间的情况下获得最大的收益(-XX:MaxGCPauseMillis设置期望的停顿时间)
4、停顿时间和吞吐量如何进行权衡?
-XX:MaxGCPauseMillis设置停顿时间,但不是越小越好,因为停顿时间果断,就会造成每次清理的空间过小,进而导致G1必须进行多次回收,从而导致吞吐量降低,而吞吐量过低,会造成垃圾的堆积,最终导致FGC,而G1的目标就是不要有FGC,与调优背道而驰,所以需要根据实际压测的情况来进行取值,一般来说,就是几百毫秒的范围。从而权衡停顿时间和吞吐量
四个阶段
初始标记:找到根对象(此时是STW,但是根对象比较少,所以STW时间较少)
并发标记:工作线程运行,不断产生垃圾或者将垃圾变成资源。这个阶段比较容易出错(最耗时的阶段,但程序在运行,这时候不产生STW)
重新标记:并发标记阶段,垃圾有变化,进行重新标记,STW,但是时间也不长
并发回收:把垃圾全部清理
三色标记
为什么要用三色标记?
异步执行,传统的基于标记清除法,在GC起价,必须STW,不能异步执行GC操作,而STW对追求实时性的系统来说是不可接收的,三色标记法就是实现了异步操作,能在线程执行的过程中实现并发标记,从而极大地减少了STW时间
并发标记算法
把对象分成三种颜色;
1、黑色:自身和成员变量均标记完成
2、灰色:自己完成标记,成员变量没有完成标记
3、白色:均未发生标记
最开始所有对象都是白色,然后将自己完标记,但是成员变量没有完成标记的对象设置为灰色,随后将灰色对象的成员变量完成标记,变成黑色。当没有灰色对象的时候就把白色对象回收掉。
漏标问题:
并发标记过程中,指向白色对象的所有灰色对象没有再指向它,并且此时黑色对象指向它(黑色指向白色且指向白色的灰色没了)。此时出现漏标,这个白色对象不是垃圾,却会被当成垃圾被回收
解决办法:
从产生漏标的条件出发
1、增量更新(IU):跟踪黑色对象指向白色对象的增加,也就是把黑色对象重新标记为灰色(CMS使用的办法)
2、原始快照(STAB):跟踪灰色对象指向白色对象的消失,也就是灰色对象指向白色对象引用消失时,把这个引用保存下来,下次扫描还能接着扫描到(将这个引用推到堆栈中),保证白色对象还能被扫描到(GC里面有个栈,这个栈里面全是灰色对象指向白色对象的引用)
为什么G1使用STAB而不使用IU?
灰色->白色引用消失的时候,引用会被push到satb_mark_queue队列中,下次扫描的时候拿到这个引用,找到对应的白色对象,查看它所在的region的RSet,看有无对象指向它,而不需要扫描整个堆就能查找到指向白色对象的引用,效率比较高,注意,是SATB配合RSet效率比较高,不然光是SATB效率高的话,CMS为什么不用SATB而使用IU。
3、优缺点
优点:
1、并发收集
2、压缩空闲时间不会延长GC的暂停时间
3、能预测GC停顿时间
4、适合不需要高吞吐量,但需要快速响应时间的程序
5、由于G1整体上是基于标记整理算法,局部(两个region之间)是基于标记复制算法,不会存在内存碎片,有利于长时间运行
缺点:
内存占用和额外的执行负载高于CMS
参考
《深入理解Java虚拟机》