很多教科书中提到,判断一个对象是否应该存活,是使用程序计数器的,对象有一个引用计数器,每当一个地方引用,计数器就加1,引用失效,计数器减1,当计数为0,该对象可以被回收。
引用计数器算法的实现简单,判定效率也很高,但是,java虚拟机里面没有选用计数器算法来管理内存,主要原因是它很难解决对象之间的循环引用问题。
public class TestGC {
public Object obj = null;
public static void main(String[] args) {
TestGC gc1 = new TestGC();
TestGC gc2 = new TestGC();
gc1.obj = gc2;
gc2.obj = gc1;
gc1 = null;
gc2 = null;
System.gc();
}
}
对象gc1、gc2已经被gc回收,如果采用计数器算法,将造成内存泄漏。
可达性分析算法
基本思想是通过一系列的“GC Roots”对象作为起点,向下搜索,搜索所经过的路径称为引用链,当一个对象到GC Roots没有任何引用引用链,此对象可以被回收
java中的引用:
jdk1.2之后,java对引用的概念进行了扩充,将分为强引用、软引用、弱引用、虚饮用四种:
强引用:类似于Object obj = new Object();强引用还存在,垃圾收集器永远不会回收被引用对象。
软饮用:描述一些还在用,但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行第二次回收。如果回收后还没有足够内存,抛出异常。
弱引用:描述非必要对象,比弱引用更弱一些,被引用关联的对象只能生存在下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉被弱引用关联的对象。
虚引用:一个对象是否存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的主要目的就是能在这个对象被收集器回收时收到一个系统通知。
要真正的宣布一个对象的死亡,需要经历两个过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将两种情况都视为“没有必要执行”。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个F-Queue的队列中,并稍后由一个虚拟机自动建立,低优先级的Finalizer线程去执行它。但并不是立即执行,因为如果finalize()方法执行缓慢或者死循环,可能导致整个内存回收系统崩溃。如果在finalize()中将this赋值一个变量,第二次标记时将会被移除“即将回收”的集合。
public class FinalEscapeGC {
public static FinalEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("FinalEscapeGC.isAlive()");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("FinalEscapeGC.finalize()");
FinalEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalEscapeGC();
//对象第一次成功自救
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("is deed");
}
//由于finalize只会被执行一次,所以自救失败
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if(SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("is deed");
}
}
}