JVM 三色标记法 读写屏障

https://blog.csdn.net/qq_21383435/article/details/106311542

首先:CMS和G1都使用了三色标记法

关于垃圾回收算法,基本就是那么几种:标记-清除、标记-复制、标记-整理。在此基础上可以增加分代(新生代/老年代),每代采取不同的回收算法,以提高整体的分配和回收效率。

无论使用哪种算法,标记总是必要的一步。这是理算当然的,你不先找到垃圾,怎么进行回收?

垃圾回收器的工作流程大体如下:能标记的都是可用的,未标记的都是垃圾

标记出哪些对象是存活的,哪些是垃圾(可回收);
进行回收(清除/复制/整理),如果有移动过对象(复制/整理),还需要更新引用。

本文着重来看下标记的部分。

  1. 初始时,所有对象都在 【白色集合】中;
  2. 将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
  3. 从灰色集合中获取对象:
    3.1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
    3.2. 将本对象 挪到 【黑色集合】里面。
  4. 重复步骤3,直至【灰色集合】为空时结束。
  5. 结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收

当Stop The World (以下简称 STW)时,对象间的引用 是不会发生变化的,可以轻松完成标记。
而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。

此刻之后,对象E/F/G是“应该”被回收的。然而因为E已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存。

这部分本应该回收 但是 没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。

另外,针对并发标记开始后的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能会变为垃圾,这也算是浮动垃圾的一部分。

此时切回GC线程继续跑,因为E已经没有对G的引用了,所以不会将G放到灰色集合;尽管因为D重新引用了G,但因为D已经是黑色了,不会再重新做遍历处理。

最终导致的结果是:G会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的

不难分析,漏标只有同时满足以下两个条件时才会发生:

  1. 条件一:灰色对象 断开了 白色对象的引用;即灰色对象 原来成员变量的引用 发生了变化。
  2. 条件二:黑色对象 重新引用了 该白色对象;即黑色对象 成员变量增加了 新的引用。

写屏障用于拦截第二和第三步;而读屏障则是拦截第一步。
它们的拦截的目的很简单:就是在读写前后,将对象G给记录下来。

这里比如开始B指向D,但是因为后来B不指向D了,由A执行D了,最简单的方法是将A变成灰色,等待下次进行再次遍历。

2.3.2 ABA问题

你女朋友和你分手了,然后又找了一个男朋友,然后又分手了,最后又和好了,而你朋友一直以为都是一个男朋友就是你。

所以CMS ,remark阶段,要重新扫描一遍。

2.3.1 写屏障(Store Barrier)

给某个对象的成员变量赋值时,其底层代码大概长这样:

【当有新引用插入进来时,记录下新的引用对象】
这种做法的思路是:不要求保留原始快照,而是针对新增的引用,将其记录下来等待遍历,即增量更新(Incremental Update)。

增量更新破坏了条件二:【黑色对象 重新引用了 该白色对象】,从而保证了不会漏标。

2.3.2 读屏障(Load Barrier)

猜你喜欢

转载自blog.csdn.net/kuaipao19950507/article/details/106821010