本篇内容主要讲解“G1垃圾回收器与CMS的区别有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“G1垃圾回收器与CMS的区别有哪些”吧!
在G1之前的垃圾收集器,将堆区主要划分了Eden区,Old区,Survivor区。其中对于Eden,Survivor对回收过程来说叫做“年轻代垃圾收集”。并且年轻代和老年代都分别是连续的内存空间。G1将堆分成了若干Region,Region的大小可以通过G1HeapRegionSize参数进行设置,其必须是2的幂,范围允许为1Mb到32Mb。 JVM的会基于堆内存的初始值和最大值的平均数计算分区的尺寸,平均的堆尺寸会分出约2000个Region。分区大小一旦设置,则启动之后不会再变化。
Eden regions(年轻代-Eden区)
Survivor regions(年轻代-Survivor区)
Old regions(老年代)
Humongous regions(巨型对象区域)
Free resgions(未分配区域,也会叫做可用分区)
1)G1还是采用分代回收,但是不同的分代之间内存不一定是连续的,不同分代的Region的占用数也不一定是固定的(不建议通过相关选项显式设置年轻代大小。会覆盖暂停时间目标)。年轻代的Eden,Survivor数量会随着每一次GC发生相应的改变。
2)分区是不固定属于哪个分代的,所以比如一次ygc过后,原来的Eden的分区就会变成空闲的可用分区,随后也可能被用作分配巨型对象,成为H区等。
3)G1中的巨型对象是指,占用了Region容量的50%以上的一个对象。Humongous区,就专门用来存储巨型对象。如果一个H区装不下一个巨型对象,则会通过连续的若干H分区来存储。因为巨型对象的转移会影响GC效率,所以并发标记阶段发现巨型对象不再存活时,会将其直接回收。ygc也会在某些情况下对巨型对象进行回收。
4)分区可以有效利用内存空间,因为收集整体是使用“标记-整理”,Region之间基于“复制”算法,GC后会将存活对象复制到可用分区(未分配的分区),所以不会产生空间碎片。
5)G1类似CMS,也会在比如一次fullgc中基于堆尺寸的计算重新调整(增加)堆的空间。但是相较于执行fullgc,G1 GC会在无法分配对象或者巨型对象无法获得连续分区来分配空间时,优先尝试扩展堆空间来获得更多的可用分区。原则上就是G1会计算执行GC的时间,并且极力减少花在GC上的时间(包括ygc,mixgc),如果可能,会通过不断扩展堆空间来满足对象分配、转移的需要。
6)因为G1提供了“可预测的暂停时间”,也是基于G1的启发式算法,所以G1会估算年轻代需要多少分区,以及还有多少分区要被回收。younggc触发的契机就是在Eden分区数量达到上限时。一次younggc会回收所有的Eden和survivor区。其中存活的对象会被转移到另一个新的survivor区或者old区,如果转移的目标分区满了,会再将可用区标记成S或者O区。
TLAB(Thread Local Allocation Buffer)本地线程缓冲区
G1 GC会默认会启用Tlab优化。其作用就是在并发情况下,基于CAS的独享线程(mutator threads)可以优先将对象分配在一块内存区域(属于Java堆的Eden中),只是因为是Java线程独享的内存区,没有锁竞争,所以分配速度更快,每个Tlab都是一个线程独享的。如果待分配的对象被判断是巨型对象,则不使用TLAB。
PLAB(Promotion Local Allocation Buffer) 晋升本地分配缓冲区
在younggc中,对象会将全部Eden区存活的对象转移(复制)到S区分区。也会存在S区对象晋升(Promotion)到老年代。这个决定晋升的阀值可以通过MaxTenuringThreshold设定。晋升的过程,无论是晋升到S还是O区,都是在GC线程的PLAB中进行。每个GC线程都有一个PLAB。
Collection Sets(CSets)待收集集合
GC中待回收的region的集合。CSet中可能存放着各个分代的Region。CSet中的存活对象会在gc中被移动(复制)。GC后CSet中的region会成为可用分区。
Remembered Sets(RSets)已记忆集合
已记忆集合在每个分区中都存在,并且每个分区只有一个RSet。其中存储着其他分区中的对象对本分区对象的引用,是一种points-in结构。ygc的时候,只要扫描RSet中的old区对象对于本young区的引用,不需要扫描所有old区。mixed gc时,扫描Old区的RSet中,其他old区对于本old分区的引用,一样不用扫描所有的old区。提高了GC效率。因为每次GC都会扫描所有young区对象,所以RSet只有在扫描old引用young,old引用old时会被使用。
Card Table 卡表
将Java堆划分为相等大小的一个个区域,这个小的区域(一般size在128-512字节)被当做Card,而Card Table维护着所有的Card。Card Table的结构是一个字节数组,Card Table用单字节的信息映射着一个Card。当Card中存储了对象时,称为这个Card被脏化了(dirty card)。 对于一些热点Card会存放到Hot card cache。同Card Table一样,Hot card cache也是全局的结构。
CMS收集器仅作用于老年代的收集,是基于标记-清除算法的,它的运作过程分为4个步骤:
初始标记(CMS initial mark)独占CPU(STW),仅标记GCroots能直接关联的对象
并发标记(CMS concurrent mark)可以和用户线程并行执行,标记所有可达对象
重新标记(CMS remark)独占CPU(STW),对并发标记阶段用户线程运行产生的垃圾对象进行标记修正
并发清除(CMS concurrent sweep)可以和用户线程并行执行,清理垃圾
其中,初始标记、重新标记这两个步骤仍然需要Stop-the-world。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始阶段稍长一些,但远比并发标记的时间短。
并发收集、低停顿
1)对CPU非常敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢
2)无法处理浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾
3)CMS使用“标记-清理”法会产生大量的空间碎片:当碎片过多,将会给大对象空间的分配带来问题,会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC,为了解决这个问题CMS提供了一个开关参数(-XX:+UseCMSCompactAtFullCollection默认开启),用于在FullGC完成之后进行一次碎片整理,但是内存整理的过程是无法并发的,会导致停顿时间变长
年轻代垃圾回收只会回收Eden区和Survivor区。YGC时,首先G1停止应用程序的执行(Stop-The-World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段。
1)第一阶段,扫描根。根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。跟引用连同RSet记录的外部引用作为扫描存活对象的入口。
2)第二阶段,更新RSet。处理dirty card queue中的card,更新RSet。此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用。
3)第三阶段,处理RSet。识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
4)第四阶段,复制对象。此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阈值会被复制到Old区中空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间。
5)第五阶段,处理引用。处理Soft,Weak,Phantom,Final,JNI Weak 等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。
当整个堆大小在jvm堆栈空间中占比达到IHOP阈值-XX:InitiatingHeapOccupancyPercent(默认45%)时,G1就会启动一次混合垃圾收集周期。Mix GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区。进行Mix GC之前,会先进行全局并发标记。
1)初始标记(InitingMark):标记GC Roots,会STW,一般会复用YoungGC的暂停时间。初始标记会设置好所有分区的NTAMS值。
2)根分区扫描(RootRegionScan):根据初始标记阶段确定的GC根元素,扫描这些元素所在region,获取对老年代的引用,并标记被引用的对象。 该阶段与应用线程并发执行,也就是说没有STW停顿,必须在下一次年轻代GC开始之前完成。
3)并发标记(ConcurrentMark):遍历整个堆,查找所有可达的存活对象。若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。 此阶段与应用线程并发执行, 也允许被年轻代GC打断。
4)最终标记(Remark):此阶段有一次STW暂停,以完成标记周期。 G1会清空SATB缓冲区,跟踪未访问到的存活对象,并进行引用处理。
5)清除阶段(Clean UP): 这是最后的子阶段,G1在执行统计和清理RSet时会有一次STW停顿。 在统计过程中,会把完全空闲的region标记出来,也会标记出适合于进行混合模式GC的候选region。 清理阶段有一部分是并发执行的,比如在重置空闲region并将其加入空闲列表时。
清除阶段之后,还会对存活对象进行转移(复制算法),转移到其他可用分区,所以当前的分区就变成了新的可用分区。复制转移主要是为了解决分区内的碎片问题。
1)并发标记结束以后,老年代中百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次(可以通过-XX:G1MixedGCCountTarget设置)被回收。
2)混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一样,只是回收集多了老年代的内存分段。具体过程请参考年轻代回收过程。
3)由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高,越会被先回收。并且有一个阈值会决定内存分段是否被回收。-XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间。
4)混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是允许整个堆内存中有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会花费很多的时间但是回收到的内存却很少。
1)并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-the-world停顿的时间,部分其他收集器原来需要停顿Java线程执行的GC操作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
2)分代收集
3)空间整合:与CMS的标记-清除算法不同,G1从整体来看是基于标记-整理算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
4)可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
到此,相信大家对“G1垃圾回收器与CMS的区别有哪些”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。