关于垃圾回收需要注意的点
🔎

关于垃圾回收需要注意的点

Created
May 26, 2021 12:16 PM
status
Published
垃圾回收是JVM中绕不开的一个话题,java中系统自动回收的特性使得很多初学者包括我都对内存的认知十分狭隘。而垃圾回收看似简单,但是还是有很多细节值得注意。

Q:JVM怎么判断那些对象应该回收?
相信大多数人都能脱口而出:引用计数法可达性分析算法
而重点在于:
两个重要的垃圾回收器:CMS和G1中的并发标记有什么问题?
在展开讲述并发标记的问题之前,首先得对可达性分析的实现方式,三色标记有一定认识。

三色标记

notion image

并发标记带来了什么问题?

众所周知,并发标记是用户线程和标记阶段共同工作,而这个标记阶段则是发生在初始标记之后,初始标记标记出了GC Roots,而并发标记则从这些GC Roots出发,继续往下标记出全部的垃圾。
由于用户线程和垃圾回收线程同时工作,那么在并发标记阶段,如果用户线程改变了垃圾之间的引用关系,那么势必会对标记结果产生影响。
无非就是两个影响:
  1. 将原本消亡的对象标记为存活,这便是浮动垃圾,下次回收的时候回收掉,也就完事了。
  1. 将原本存活的对象标记为消亡,这便是对象消失,这个后果就不太乐观了。

对象消失

对象消失永远都有以下两个条件
  1. 赋值器插入了一条或者多条从黑色对象到白色对象的新引用。
  1. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
所以解决对象消失只需要破坏其中一个条件即可。

CMS采用的增量更新

CMS采用增量更新,破坏了第一个条件来避免对象消失。
当出现了新的黑色指向白色的引用的时候,会将此次引用记录下来,在最终标记的时候以这些黑色对象为根,再扫描一次。

G1采用的SATB(Starting at the beginning)原始快照

G1采用STAB来解决对象消失的问题。
SATB就是当灰色对象要删除指向白色对象的引用关系时,就将该白色对象改变为非白的,再重新扫描。(可能会产生浮动垃圾)

垃圾回收只有这些问题?

当然不是了,还有跨代引用的问题。

跨代引用

当新生代中一个对象只被老年代对象引用的时候,就产生了这个问题。 只扫描新生代的话,这个对象就是一个孤儿,但是为了避免错误回收他而去扫描老年代看看有没有老年对象引用它,这显然是不现实的。

解决方法:RSet

rememberSet是在新生代中存在的一个数据结构。 其实是一个指针的集合,指向引用了自己的老年代对象所在的区域。

具体实现:卡精度:CardTable

卡表就是将老年代(G1的话是一个个region)划分为一张张大小为512字节的卡,并维护一个卡表。当某一张卡中的对象引用了新生代的对象的时候就将这张卡标为脏卡
Minor GC的时候就不需要扫描整个老年代,只需要找到脏卡,将脏卡中的对象加入到GC Roots中就可以了,当扫描完毕后清楚所有脏卡的标识。
把所有存活对象复制到S1区的时候,会将引用对象也一并复制,这时候重新设置卡的标识位。

卡表的问题

并发标记时用户线程和标记线程同时运行,那么卡表的情况也会发生变化,所以卡表也是动态维护的。

写屏障

在引用关系发生变化的时候,都会对卡表进行中的卡进行扫描维护。
notion image
记得看图!
notion image
绿色时:启动Refinement线程,开始更新RS
黄色时:全部Refinement线程启动
红色时:应用队列也来处理

G1垃圾回收器怎么知道你是什么时候的垃圾

假设用户线程在并发标记阶段执行时不仅修改了对象引用关系,还新分配了新对象,G1是如何找到并处理这些对象的呢?
notion image
如图所示,NextTAMSTop 之间就是新分配的对象了。

Loading Comments...