垃圾收集算法,三色标记,读写屏障
2021-10-20 20:12:09 0 举报
垃圾收集算法,三色标记,读写屏障
作者其他创作
大纲/内容
应用程序线程
E
存活对象
保留内存
Serial
Humongous
垃圾收集算法
O
白色:表示对象尚未被垃圾收集器访问过。 显然在可达性分析刚刚开始的阶段, 所有的对象都是白色的, 若在分析结束的阶段, 仍然是白色的对象, 即代表不可达。
D
年轻代
每个对象有一个64位指针,这64位被分为: 18位:预留给以后使用;1位:Finalizable标识,此位与并发引用处理有关,它表示这个对象只能通过finalizer才能访问;1位:Remapped标识,设置此位的值后,对象未指向relocation set中(relocation set表示需要GC的Region集合);1位:Marked1标识;1位:Marked0标识,和上面的Marked1都是标记对象用于辅助GC;42位:对象的地址(所以它可以支持2^42=4T内存):
GC线程应用程序线程暂停
S
标记清除算法
复制算法的缺点就是只有一半的空间是可用的。
新生代
hotspot使用一种叫做“卡表”(cardtable)的方式实现记忆集,也是目前最常用的一种方式。关于卡表与记忆集的关系,可以类比为Java语言中HashMap与Map的关系。卡表是使用一个字节数组实现:CARD_TABLE[ ],每个元素对应着其标识的内存区域一块特定大小的内存块,称为“卡页”。 hotSpot使用的卡页是2^9大小,即512字节
并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop\u0002The-World停顿时间。部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段(通过参数\"-XX:MaxGCPauseMillis\"指定)内完成垃圾收集。
ParNew
可用内存
Parallel old用于老年代,它是多线程的,在垃圾回收的时候,会STW,也就是应用程序停止,但是它区别于Serial的是它在回收垃圾的时候是多线程的。缺点:单线程,STW;优点:高效率的利用CPU
Remapped
写屏障实现增量更新:当对象A的成员变量的引用发生变化时,比如新增引用(a.d = d),我们可以利用写屏障,将A新的成员变量引用对象D记录下来
Marked1
Parallel Scavenge用于年轻代,它是多线程的,在垃圾回收的时候,会STW,也就是应用程序停止,但是它区别于Serial的是它在回收垃圾的时候是多线程的。缺点:单线程,STW;优点:高效率的利用CPU
灰色:表示对象已经被垃圾收集器访问过, 但这个对象上至少存在一个引用还没有被扫描过。
读屏障:读屏障是直接针对第一步:D d = a.b.d,当读取成员变量时,一律记录下来:
卡表
写屏障 + 增量更新
STW
根据对象生命周期的不同,将内存划分为几块。比如大部分的对象都是朝生夕死的对象,所以就放到了新生代,而剩下的则放入老年代。
可回收内存
Finalizable
C
B
老年代
Epsilon
1、初始标记(initial mark,STW):暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快2、并发标记(Concurrent Marking):这个阶段不会STW,应用线程和垃圾回收线程一起运行,这个阶段耗时很长,但是不会STW,所以 用户的体验不差,这个阶段主要是大致标记从GCRoots的直接关联对象开始到整个对象图的过程。这个阶段,因为用户线程的继续运行,可能会导致已经标记过得对象发生改变; span style=\"font-size: inherit;\
eden
并发重置
内存整理后
记忆集
Serial用于年轻代,它是单线程的,在垃圾回收的时候,会STW,也就是应用程序停止。缺点:单线程,STW;优点:效率高,因为垃圾收集的时候,别的线程全部STW
CMS
ParNew用于年轻代,它是多线程的,在垃圾回收的时候,会STW,也就是应用程序停止,但是它区别于Serial的是它在回收垃圾的时候是多线程的。缺点:单线程,STW,用户体验不好
Serial old用于老年代,它是单线程的,在垃圾回收的时候,会STW,也就是应用程序停止。缺点:单线程,STW;优点:效率高,因为垃圾收集的时候,别的线程全部STW同时,它会作为CMS的兜底
并发清理
Parallel Scavenge
第一行代码我们尝试读取堆中的一个对象引用obj.fieldA并赋给引用o(fieldA也是一个对象时才会加上读屏障)。如果这时候对象在GC时被移动了,接下来JVM就会加上一个读屏障,这个屏障会把读出的指针更新到对象的新地址上,并且把堆里的这个指针“修正”到原本的字段里。这样就算GC把对象移动了,读屏障也会发现并修正指针,于是应用代码就永远都会持有更新后的有效指针,而且不需要STW。那么,JVM是如何判断对象被移动过呢?就是利用上面提到的颜色指针,如果指针是Bad Color,那么程序还不能往下执行,需要「slow path」,修正指针;如果指针是Good Color,那么正常往下执行即可:后面3行代码都不需要加读屏障:Object p = o这行代码并没有从堆中读取数据;o.doSomething()也没有从堆中读取数据;obj.fieldB不是对象引用,而是原子类型。
Unused (18 bits)
1、初始标记:这个阶段是会STW的,标记出GCRoots能够直接引用到的对象,速度很快;2、并发标记:这个阶段不会STW,应用线程和垃圾回收线程一起运行,这个阶段耗时很长,但是不会STW,所以 用户的体验不差,这个阶段主要是大致标记从GCRoots的直接关联对象开始到整个对象图的过程。这个阶段,因为用户线程的继续运行,可能会导致已经标记过得对象发生改变;3、重新标记:重新标记会STW,主要是为了标记那些在并发标记阶段因为用户线程继续运行导致已经标记过的对象发生改变,主要用到了三色标记里的标量更新算法做重新标记;4、并发清理:开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理;5、并发重置:重置本次GC过程中的标记数据。
H
A
标记整理算法
跨代引用
gc roots(a)
重新标记
1.50%以上的堆被存活对象占用2. 对象分配和晋升的速度变化非常大3. 垃圾回收时间特别长,超过1秒4. 8GB以上的堆内存(建议值)5. 停顿时间是500ms以内
Marked0
old
写屏障实现SATB :当对象B的成员变量的引用发生变化时,比如引用消失(a.b.d = null),我们可以利用写屏障,将B原来成员变量的引用对象D记录下来
Shenandoah
Serial old
颜色指针的三大优势:1. 一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理,这使得理论上只要还有一个空闲Region,ZGC就能完成收集。2. 颜色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障。3. 颜色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。
问题
JVM将堆等大小的分为了多个Region,JVM最多可以有2048个Region,如果堆的大小是4096M的话,那么每个Region就是2M。年轻代的默认占比是5%,也就是假如堆是2048M的话,那么年轻代大概就是100个Region。eden与Survivor的比为8:1:1,也就是说如果年轻代有100个Region,那么eden区战80,每个Survivor占10。当我们的对象大小大于Region大小的50%的话,就视为大对象,会放入到Humongous中。
初始标记
黑色:代表已经被垃圾回收器访问过。并且所有的对象引用都已经被扫描过,黑色的对象代表已经扫描过, 它是安全存活的, 如果有其他对象引用指向了黑色对象, 无须重新扫描一遍。 黑色对象不可能直接(不经过灰色对象) 指向某个白色对象。
内存整理前
ZGC的Region具有大、 中、 小三类容量:小型Region(Small Region) : 容量固定为2MB, 用于放置小于256KB的小对象。中型Region(Medium Region) : 容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。大型Region(Large Region) : 容量不固定, 可以动态变化, 但必须为2MB的整数倍, 用于放置4MB或以上的大对象。 每个大型Region中只会存放一个大对象, 这也预示着虽然名字叫作“大型Region”, 但它的实际容量完全有可能小于中型Region, 最小容量可低至4MB。 大型Region在ZGC的实现中是不会被重分配(重分配是ZGC的一种处理动作,用于复制对象的收集器阶段, 稍后会介绍到)的, 因为复制一个大对象的代价非常高昂
对于这种情况,因为黑色的已经被访问过了,所以已经不用再访问黑色对象了,但是此时的D对象断开了B的引用又有了A的引用,所以不应该是垃圾对象,不应该被回收。
并发标记
分代收集理论
Parallel old
G1垃圾收集分类
Survivor
在新生代做GCRoots可达性扫描过程中可能会碰到跨代引用的对象,这种如果又去对老年代再去扫描效率太低了。为此,在新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合),避免把整个老年代加入GCRoots扫描范围。垃圾收集场景中,收集器只需通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针即可,无需了解跨代引用指针的全部细节。
漏标-读写屏障漏标会导致被引用的对象被当成垃圾误删除,有两种解决方案: 增量更新(IncrementalUpdate) 和原始快照(Snapshot At The Beginning,SATB) 。增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向白色对象的引用之后, 它就变回灰色对象了。原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)以上无论是对引用关系记录的插入还是删除, 虚拟机的记录操作都是通过写屏障实现的。
标记清除算法:标记那些存活的对象,然后在内存回收的时候,将可回收的对象清除掉。缺点:1.效率问题:如果标记的对象太多的话,效率就较低;2.空间碎片问题:因为没有进行空间的整理,所以说会有空间碎片话问题。
G1
复制算法
标记整理算法:标记那些存活的对象,然后在内存回收的时候,将可回收的对象清除掉,并且改变存活对象的引用,将它们挪到内存中的一边。好处:解决了内存碎片化问题。
场景
漏标
YoungGC:YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做YoungGC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GCMixedGC不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GCFull GC停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了)
ZGC
优点:停顿少,用户体验好,并发收集;缺点:①会和服务抢资源②会有浮动垃圾,只能放到下一个进行处理③使用标记清理算法,会有大量的空间碎片④在并发标记和并发清理阶段会出现系统一边运行,一边回收的情况,也许没回收完就再次触发full gc,也就是\"concurrentmode failure\",此时会进入stop the world,用serial old垃圾收集器来回收
就是在并发标记阶段,由于方法运行结束导致局部表里GCRoots被销毁,但是这个GCRoots引用的对象之前又被扫描过了,但是它在本次垃圾回收的时候是不会被回收掉的,会在下一次进行回收,这部分对象就是浮动垃圾。另外,针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。
0 条评论
下一页