1. Java垃圾回收判断对象能否存活的办法及产生的问题
(1)Java判断对象是否存活的方法
在主流的垃圾回收器中,判断一个对象是否应该被回收采用的是可达性分析法,通过对一系列被称为GCRoot的对象为起始搜索点,根据其引用关系向下搜索,走过的路径称为引用链,对引用链上的对象进行标记,被标记的对象为存活对象
(2)具体的标记方法
主流的垃圾回收器是采用:初始标记 =》 并发标记 =》 重新标记 的方式来确认存活对象的;
其中初始标记和重新标记是单线程标记,也就是说会阻塞用户线程,而并发标记则会与用户线程一起执行,这种标记方式有以下的好处和坏处:
- 好处:减少标记消耗。
-
- 初始标记是对与GCRoot对象直接相连的对象进行标记,由于需要标记对象不多,这个过程非常短暂,对用户线程的阻塞是可以接受的;
-
- 并发标记则是标记与GCRoot间接相连的对象,这个过程需要标记的对象较多,耗时较长,但由于是并发标记,用户线程不需阻塞,影响很小
-
- 重新标记用于修复并发标记阶段因为用户线程修改引用关系而产生了错误标记
- 坏处:会产生了浮动垃圾以及漏标存活对象
如前文所说,并发标记期间用户线程会对对象的引用关系进行修改,从而导致多标和错标的情况。 -
- 对于多标的对象就称为浮动垃圾,这部分垃圾可以留待下次回收;
-
- 但漏标则会使得一些存活对象被判断为垃圾,这对程序来说是致命错误,因此重新标记主要解决的就是这种漏标情况,那么,它是怎么解决的呢?
(3)重新标记解决漏标的方法
对于这个问题,我们可以将堆中对象划分为白色、灰色以及黑色三种
- 白色:代表尚未被搜索到的对象,整个搜索过程结束后还是白色的对象就是可回收的
- 灰色:代表搜索中的对象,本身已经被搜索到了,但其下方引用链尚未搜索完毕
- 黑色:确认存活对象,本身及其下方引用链都被搜索过了
产生漏标需要满足以下两个条件:
- 用户线程在并发标记期间插入一条从黑色对象到白色对象的引用
- 用户线程取消了所有从灰色对象到该白色对象的直接或间接引用
而要防止漏标则只需要破坏其中一个条件就可以了,接下来要介绍两种方式分别破坏了其中一个条件
- 增量更新:破坏第一个条件,如果一个黑色对象插入了新引用,则保存这段引用,并发标记结束后从插入该引用的黑色对象重新开始标记;相当于将插入新引用的黑色对象变为灰色
- 原始快照:破坏第二个条件,如果一个灰色对象删除了直接或间接引用,则记录这个被删除的引用,并发标记结束后从所有删除了直接或间接引用的灰色对象开始搜索标记;也就是不管被删除引用的对象最终是否存活,都将其重新标记,让其存活;若该对象最终存活,则不会漏标;若最终不存活,则该对象成为浮动垃圾,下次再回收
以上两种防止漏标的分别在CMS和G1垃圾回收器中应用,CMS采用的是增量更新,而G1采用的是原始快照。