JVM-三色标记算法
三色标记算法是一种垃圾回收的标记算法。它可以让JVM不发生或仅短时间发生STW(Stop The World),从而达到清除JVM内存垃圾的目的。JVM中的CMS、G1垃圾回收器 所使用垃圾回收算法即为三色标记法。
三色标记过程:
黑色:代表该对象以及该对象下的属性全部被标记过了。(程序需要用到的对象,不应该被回收)
灰色:对象被标记了,但是该对象下的属性未被完全标记。(需要在该对象中寻找垃圾)
白色:对象未被标记(需要被清除的垃圾)
三色标记存在的问题:
对象漏标:
如果已经被C已经被标记为黑色了,因为是并发标记,此时可能会有线程在C中引用D。此时由于C已经被标记为黑色,不会再扫描D。D会被认为需要回收,此问题会导致系统出问题。
CMS 和 C1采用了两种方式来解决上面的问题
CMS(Concurrent Mark Swap):
以获取最短回收停顿时间为目标的收集器。基于标记-清除算法实现。
运行过程:
由初始标记->并发标记->再次标记->并发清理实现
并发清理时,直接删除掉标记阶段判断为垃圾的对象,因为不需要移动存活的对象,所以可以与用户线程同时并发。
如何解决漏标
在应对漏标问题时,CMS使用了增量更新的方法(Increment Update)
当未被标记的对象被重新引用后,引用它的对象如果是黑色的话,那么会将颜色置为灰色,在二次标记的时候让GC线程继续标记它的属性对象
CMS的缺点:
1、浮动垃圾:CMS采用了标记-清除的算法,最终必然产生许多的内存碎片,当到达一定数量时,CMS无法清理这些碎片,会启动Serial Old垃圾回收器(标记整理算法)来清理这些垃圾。serial Old是单线程操作进行垃圾清理,效率很低。
针对上述问题 可以考虑采用Mark-Sweep-Compact压缩算法,减少垃圾碎片
-XX:+UseCMSCompactAtFullCollection 开启CMS的压缩
-XX:CMSFullGCsBeforeCompaction 默认为0,指经过多少次CMS FullGC才进行压缩
2.还有一种情况会触发SerialOld ,在使用CMS进程并发清理内存是可能发生OOM的问题。不得不进行SerialOld,针对这个问题可以降低触发CMS GC的阈值,使浮动垃圾不那么容易占满老年代。
-XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让老年代占用率达到该值就进行CMS GC
G1(Garbage First)
G1(Garbage First)物理内存不再分代,而是由一块一块的Region 组成,但是逻辑分代仍然存在。G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize 设定,取值范围为1MB~32MB,且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待
CardTable
在YGC是,我们在对一个对象是否被引用的过程,如果这个对象被Old区的对象所引用,那么我们就需要扫描整个Old区,这显然会耗费很大的时间。所以JVM设计了CardTable ,将Old区分为一个一个Card,一个Card有多个对象;如果一个Card中的对象有引用指向Young区,则将其标记为Dirty Card ,下次需要进行YoungGC 时,只需要去扫描Dirty Card 即可。
Card Table在底层数据结构以BitMap实现
RSet(Remembered Set)
是辅助GC过程的一种结构,典型的空间换时间工具,和Card Table有些类似。
后面说到的CSet(Collection Set)也是辅助GC的,它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。
在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。
而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。每个Region 中都有一个RSet ,记录其他Region 到本Region 的引用信息;使得垃圾回收器不需要扫描整个堆找到谁引用当前分区中的对象,只需要扫描RSet即可。
新生代与老年代的比例
5% - 60% ,一般不使用手工指定,因为这是G1预测停顿时间的基准,这地方简要说明一下,G1可以指定一个预期的停顿时间,然后G1会根据你设定的时间来动态调整年轻代的比例,例如时间长,就将年轻代比例调小,让YGC尽早行
如何解决漏标:
在应对漏标时,采用了SATB (snapshot at the beginning)
- 在标记开始的时候成成一个快照图标记存活对象
- 在一个引用断开后,要将此引用推到GC的堆栈中,保证对象还能被GC线程扫描到(通过在 wirte barrier 里把所有旧的引用所指向的对象都变成非白的)
- 配合Rset,去扫描哪些Region引用到当前的白色对象,若没有引用到当前对象,则回收
SATB效率高于增量更新的原因
因为SATB在重新标记环节只需要去重新扫描那些被推到堆栈中的引用,并配合Rset 来判断当前对象是否被引用来进行回收;
并且在最后G1 并不会选择回收所有垃圾对象,而是根据Region 的垃圾多少来判断与预估回收价值(指回收的垃圾与回收的STW 时间的一个预估值),将一个或者多个Region 放到CSet 中,最后将这些Region 中的存活对象压缩并复制到新的Region 中,清空原来的Region 。
G1会不会进行FullGC?
会,当内存空间不足就会进行FullGC。
解决方案:
针对上面的描述我们也可以进行下概述
CMS和G1有什么区别
-
作用范围 CMS针对老年代。可以配合Serial 或 Parallel New这些新生代的垃圾回收器。如果碎片过多,老年代会使用Serial Old进行垃圾回收 G1则是针对 老年代和新生代 -
停顿时间 CMS以最小停顿时间为目标 G1可以预测垃圾回收时间 -
垃圾碎片 CMS收集器使用标记-清除算法进行垃圾回收,容易产生碎片 G1由于使用了Region的概念,降低了内存碎片 -
大对象处理 除了上面优点之外,还有一个优点,那就是对大对象的处理。在CMS内存中,如果一个对象过大,进入S1、S2区域的时候大于改分配的区域,对象会直接进入老年代。G1处理大对象时会判断对象是否大于一个Region大小的50%,如果大于50%就会横跨多个Region进行存放
|