一、判断对象是否存活
1.引用计数算法
给每个对象添加一个引用计数器,当有一个地方应用它时计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象不可能再被使用。
缺点:
- 循环引用的场景下无法实现回收,例如下面的图中,ObjectC和ObjectB相互引用,那么ObjectA即便释放了对ObjectC、ObjectB的引用,也无法回收。HotSpot在实现GC时未采用这种方式。
2. 可达性分析算法
通过一系列的称为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链(Reference Chain),当一个对象到GC Root不可达时,则证明这个对象是不可用的。如下图,对象object5、 object6、 object7,虽然相互有关联但是它们到GC Root不可达,所以它们被判定为是可回收的对象。当前主流虚拟机 实现GC时都是采用这种方式。
在Java语言中,可作为GC Root对象包括以下几种:
- 虚拟机栈(栈帧中的本地变量表)中的引用对象。
- 方法区中的静态属性或常量引用的对象。
- 本地方法栈中JNI引用的对象。
二、几种垃圾搜集算法
1.标记—清除算法
最基础的搜集算法,首先标记出所有需要回收的对象,在标记完成后统一回收。
不足:
- 标记和清除效率不高。
- 标记清除后会产生大量不连续的内存碎片,导致以后分配较大对象时内存不足。
标记—清除算法示意图:
2.复制算法
它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法代价是将内存缩小为原来的一半,未免太高了些。 复制算法示意图:
现代商业虚拟机都采用这种搜集算法来回收新生代,通常并不需要按照1:1的比例来划分内存空间,而是将内存空间划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor空间。当回收对象时,将存活对象放到另一块Survivor空间上。 HotSpot虚拟机默认Eden和Survivor空间比例为8:1:1, 所以每次新生代中可用内存空间为新生代总内存的90%,10%被浪费掉。当那10%不够放下Eden和Survivor空间中的存活对象时,会向老年代空间进行分配担保,即借用老年代内存。
3.标记—整理算法
为应对被使用内存中的所有对象都100%存活下来的极端情况,所以在老年代中一般不能直接使用“复制算法”。而根据老年代特点提出了“标记—整理(Mark-Compact)算法”,标记与“标记—清除算法”中的标记相同,但 后续操作不是直接清理对象, 而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。
示意图:
4.分代搜集算法
当代商业虚拟机的垃圾搜集都采用“分代搜集(Generational Collection)算法”,即将Java堆分为新生代和老年代,新生代中对象存活时间短,所以采用“复制算法”,而老年代对象生命周期长,所以采用“标记—整理算法”。