跟各种垃圾回收器没有太大的关系,主要有用于优化分代模型快速扫描garbage,在可达性分析判断eden区某个对象是否还有引用指向,如果指向eden区对象引用在old区,那么在判断eden区的某个对象是否存活,就需要遍历整个老年代,也就是做一次YGC需要扫描整个old区,就会极大的消耗性能。
为了优化性能,在JVM内部把Young区和Old区切分成了一个一个Card,类似于操作系统把内存切分成很多Page,这些对象存在于一个一个Card里面,在整个程序的演化过程中,如果old区的某个card里有对象指向Eden区的对象,那么就会把这个card标记成DirtyCard,这些card会有一个bitmap来表示,在查找DirtyCard的时候扫描这个位图就可以快速的查找了,这个bitmap就是Card Table。
目的就是不需要全量扫描Old区。
闻名而知雅意,收集集合,就是一组可被回收的Region集合,G1会回收垃圾特别多的Region,这些Region的信息集合,就是Collecation Set。
每一个Region里都有一个RememberedSet,RSet里面记录着另外一个Region里的对象指向当前Region对象的引用。
RSet的价值在于使垃圾回收器不需要扫描整个堆就能找到是哪个对象对当前Region里的对象进行了引用
缺点也是有的,那就是每个region都维护一个RSet,比较浪费空间,在ZGC里已经没有RSet了,取而代之的是Color Pointor(颜色指针)。
概念区分:CardTable的作用和RSet作用冲突吗?
这里是我写到这里临时想到的一个问题,
参考:https://blog.csdn.net/luzhensmart/article/details/106052574
指的是超过Region的百分之50,或者跨越多个Region的大对象。
垃圾回收器有很多,以前我们常见的垃圾回收器组合有Serial+SerrialOld ,Parallel Scavenge+ParallelOld,ParNew+CMS,这些都是逻辑+物理分代的,就是只有一个Young区和一个Old区,随着需求的增加,heap需要管理的范围也越来越大,导致了每次GC需要扫描也越来越耗时。
而G1回收器只是逻辑分代,并且为了在更大heap范围拥有高速回收的效率,G1采用了分而治之的思想。G1把整个heap切分成了很多的Region,每个Region大小为2的N次幂M,而每个Region都有可能是Eden区,Survivor区,Old区和Humongous区。
复用了之前框架分代的逻辑,但是在物理上不需要连续,这样带来的好处就是有的Region垃圾特别多,有的Region垃圾特别少,G1会优先回收垃圾对象特别多的Region,这样就节省了一大部分扫描的时间。这就是G1(Garbage First)名称的由来。
新生代依然是满了对新生代进行回收,整个新生代的对象,要么晋升,要么回收,至于新生代也采用Region分区的原因是为了跟老年代策略统一,方便调整代的大小。
在回收老年代的Region,是采用的copying算法,把存活的对象copy到另外一个空闲的Region,这个copy的过程就实现了局部的压缩,copy之后会把原来Region整体的kill掉。
并且新老年代比例,以前是1:2,而G1不需要指定,G1会跟踪每一次停顿(STW), 根据STW动态调整,调整范围是5%-60%
G1的GC分为三种:
年轻代GC,Eden区不足了,就会触发。根据STW动态调整eden区,可能会让Eden区所拥有的Region更多一些或者更少一些。多线程并行执行。
相当于一个CMS,默认触发阈值是45%(可以调整),回收额时候不分Young Old区,是混合回收的。
分为:1.初始标记(STW):标记根对象。
2.并发标记:从根对象开始找,有引用的都标记出来。
3.最终标记(STW):把哪些漏标的,新产生的标记出来。
4.筛选回收(STW):就是被回收的region中的存活的对象copy到另外一个空闲的Region,这个copy的过程就实现了局部的压缩,copy之后会把原来Region整体的kill掉,碎片也就有没有CMS那么多。
G1在堆空间分配不下的时候会产生FGC,但是尽量不要让G1产生FGC,解决方案:
1.扩内存
2.提高CPU性能
3.降低MixedGC的阈值。让MixedGC提早触发。
JAVA10之前FGC都是单线程串行的,之后才是并行的。
CMS和G1调优的目标就是不要有FGC的发生。
无论是CMS还是G1都采用了三色标记算法,他们的难点都在于并发标记,在对象的标记过程中,对象引用的关系正在发生改变,容易产生漏标的现象。
达到漏标的条件:在并发标记的过程中,B到C的引用被kill掉,并且A和C建立了引用。这个时候由于A已经是被标记为黑色,所以在最终标记的时候A不会被扫描到,B也没有引用指向C,C就会被漏标从而被当成garbage回收掉。
解决方法:把B到C的引用被kill掉,并且A和C建立了引用。这两个条件之一打破就可以了。
CMS使用的是incremental update算法,A建立C引用的时候从新把A标记为灰色,最终标记的时候从新扫描A对象的fields。
G1使用的是SATB(snapshot at the beginning)算法,关注引用的删除,满足B到C的引用消失时,把这个引用推送到GC的堆栈,保证C还能被GC扫描到。两种算法的区别在意,增量算法关注A指向C的增加,SATB关注B指向C的消失。
最终标记会扫描GC堆栈中的引用找到对象进行扫描是否有引用指向这个对象(is garbage?)
,这个时候只需要到这个对象的Region中扫描RSet就能快速的判断这个对象到底是不是垃圾了。很大的提升了效率。
按序号详解:
- 启动程序设置的一些参数,堆大小固定50M,启用CMS垃圾回收器,打印GC日志详情,查看程序参数
- 显示启动参数
- GC的原因,分配失败
- Young区回收,使用的是PN回收器
- Young区 GC前使用情况 --> GC后使用情况(Yonug区总大小)
- GC所用时间
- Young区GC前和GC后所占使用情况,以及整个heap大小
- CMS初始标记阶段,会产生STW
- 老年代的使用情况(老年代的总大小),到达这个临界值触发CMS,默认值是68%
- 整个heap的使用情况(整个heap的大小)
- 并发标记开始
- 标记Card为Dirty,也称为Card Marking
- 最终标记(从新标记)
- YG occupancy:年轻代占用及容量
- STW存活的对象进行标记
- 卸载掉没有用的class,就是对Method Area进行清理
- 阶段过后,老年代的使用情况,以及整个heap的使用情况
- 开始并发清理
- 重置内部结构,为下次GC做准备
- 新一轮的初始标记
- YGC,GC pause 暂停,发生在Young区,Evacuation表示复制存活对象
- 表示MixedGC初始标记,YGC和MixedGC混在一起了
- 表示GC工作线程
- 扫描root对象
- 更新RSet
- 扫描RSet
- Eden区回收前和回收后的状况,Survivor区回收前和回收后的状况,以及整个heap回收前和回收后的状况
- 混合回收阶段,并发扫描,标记
- FGC的原因,回收前后空间对比,总空间以及时间
- 和7一样
- 元数据区(方法区)的回收情况
- YGC,但是这里没有init mark,表示YGC只是可能和MixedGC混合执行的。
戏入人生
希望和大家一起学习进步,如果我朋友理解不到位的地方,请留言指正,谢谢。