引用计数算法
当new一个对象,这个对象就分配了一个引用计数器且计数设为1,当这个对象被其他变量引用时,对象引用计数+1;当一个对象的引用超过生存期或者被设置一个新的值时,这个对象的引用计数减1。当对象的引用计数变为0时,就标记为可回收,通知GC收集器回收。
优点
引用计数算法的实现简单,判断效率也很高
缺点
对象之间相互循环引用的问题,导致对象无法被GC回收
再来看下循环引用事例及解决办法
public static void main(String[] args) {
GcObject objA = new GcObject();
GcObject objB = new GcObject();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
}
class GcObject{
public Object instance=null;
}
第2行代码 objA引用计数为1
第3行代码 objB 引用计数为1
第4行代码 objB应用计数为2
第5行代码 objA引用计数为2
第6行代码 objA引用计数减1,引用计数为1
第7行代码 objB引用计数减1,引用计数为1
到此objA,objB引用计数都不为0,实例分配的内存都不能释放,造成内存泄漏。
解决的办法
可以通过弱引用解决循环引用的问题,弱引用是不会使引用计数+1的,这种特殊的弱引用被称为归零弱引用,这里假设objA弱引用的话,第5行代码后 objA引用计数仍然为1,第6行代码后 objA引用计数为0,objA被GC回收后objB引用计数为1,第7行代码后objB引用计数为0,被GC回收。
可达性算法
通过一系列的被称为“gc roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到“gc roots”没有任何引用链相连时,则证明此对象是不可用的。
可以作为“gc roots”的对象
(1)虚拟机栈(栈针中的局部变量表)中引用的对象
(2)方法区中类静态属性引用的对象。
(3)方法区中常量引用的对象
(4)本地方法栈中JNI引用的对象。
从上图可以reference1,reference2,reference3都是GC Roots,可以看出:
reference1->对象实例1
reference2->对象实例2
reference3->对象实例4
reference3->对象实例4->对象实例6
可以得出对象实例1,2,4,6都具有GC Roots可达性,不能被GC回收的对象,而对于对象实例3,5直接虽然连通,但并没有任何一个GC Roots与之连通,这便是GC Roots不可达对象,GC回收的对象。
复制算法
原理:它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点:不会出现碎片问题。
缺点:暂停整个应用,需要2倍的内存空间。
标记 -清除算法
原理:对于“活”的对象,一定可以追溯到其存活在堆栈、静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。第一阶段:从GC roots开始遍历所有的引用,对有活的对象进行标记。第二阶段:对堆进行遍历,把未标记的对象进行清除。这个解决了循环引用的问题。
缺点:1、暂停整个应用;2、会产生内存碎片。
记-压缩算法
原理:第一阶段标记活的对象,第二阶段把为标记的对象压缩到堆的其中一块,按顺序放。
优点:1、避免标记扫描的碎片问题;2、避免停止复制的空间问题。
分代收集算法
原理:基于对象生命周期分析得出的垃圾回收算法。把对象分为年轻代、年老代、持久代,对不同的生命周期使用不同的算法进行回收。