1.垃圾回收回顾
1.1 概念:
垃圾回收算法目前常见的也就那么几种,如:标记-清除、标记-复制、标记-整理。在此基础上可以采用分代管理(新生代、老年代、永久代),每代采用不同的垃圾回收算法,以提高整体的回收效率。
1.2 步骤:
- 标记存活对象和可回收对象(可达性分析算法、计数法)
- 对可回收对象采用进行垃圾回收处理
1.3 标记方法:
- 可达性分析算法:可达性分析算法是以根对象集合(GC Roots)为起始点,按照从上至下的方式进行搜索,搜索所走过的路径称为引用链(Reference Chain),通过判断对象与GC Roots之间是否有引用链相连来判断该对象是否需要回收,如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象。
- 引用计数算法:为每一个对象分配一个计数器,当这个对象被另一个对象引用时,这个计数器就加一;当被另一个对象取消引用时,计数器就减一。当这个计数器的值为零时,就表示当前对象没有被任何对象所引用,那么这个对象就可以被垃圾回收器进行回收了。该方法实现起来非常简单,但是有一个致命的缺点,那就是不能解决各对象之间循环引用的问题。
2.三色标记法
2.1 基本概念:
根据可达性分析,从GC Roots开始进行往下进行,可达的则为存活对象 我们把搜索过程中遇到的对象,按是"否访问过"这个条件标记成以下三种颜色:
- 白色:尚未访问过。
- 黑色:本对象已访问过,而且本对象引用到的其他对象也全部访问过了。
- 灰色:本对象已访问过,但是本对象引用到的其他对象尚未全部访问完。
2.2 过程
- 开始时,将所有的对象都加入到【白色集合】中去。
- 将GC roots从【白色集合】中转移到【灰色集合】中去
- 将GC roots直接引用到的所有对象转移到【灰色集合】中,同时将GC roots转移到【黑色集合】中去。
- 从【灰色集合】中获取对象:
将该对象 引用到的 其他对象 全部挪到 【灰色集合】中; 将该对象 挪到 【黑色集合】里面。 - 重复步骤3,直至【灰色集合】为空时结束。
- 结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收。
第一步程序将会Stop The World,这时候对象间的引用是不会发生变化的,可以轻松完成标记。而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化,多标和漏标的情况就有可能发生。
2.3 多标-浮动垃圾
如上图所示,假设引用链已经走到E(灰色集合)了,这个时候应用执行了objD.fieldE = null ,此时D与E之间的引用链将断开,此刻之后,对象E/F/G是“应该”被回收的。然而因为E已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存。 这部分本应该回收 但是 没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性,只是需要等到下一轮垃圾回收中才被清除。
另外,针对并发标记开始后的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能会变为垃圾,这也算是浮动垃圾的一部分。
2.4 漏标-读写屏障
如上图所示,假设搜索到引用链已经走到E(灰色集合),此时执行代码:
var G = objE.fieldG;
objE.fieldG = null;
objD.fieldG = G;
此时切回GC线程继续跑,因为E已经没有对G的引用了,所以不会将G放到灰色集合;尽管因为D重新引用了G,但因为D已经是黑色了,不会再重新做遍历处理。 最终将会导致G会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。
漏标需同时满足以下两个条件: 1.【灰色集合】对象断开对【白色集合】对象的引用。 2.【黑色集合】对象在GC期间重新引用【白色对象】
解决漏标—读写屏障
|