确定对象是否可以回收了
在堆里面存放了几乎所有的对象实例,垃圾收集器在对堆进行来及回收之前,需要确定哪些对象可以被回收了(即,不可能再被任何途径使用了)。
引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用了。
很多人,是这样解释引用计数算法的。
引用计数算法的实现简单,判定效率也很高。主流的Java虚拟机没有采用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。
可达性分析算法
这是主流的实现方式。基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
可作为GC Roots的对象包括以下几种:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI(即一般说的Native方法)引用的对象
当一个对象不可达时
当一个对象不可达时,也并不意味着它非死不可。
如果对象在进行可达性分析之后,发现没有与GC Roots相连接的引用链,那么它会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机都将这两种情况视为“没有必要执行”。
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列中,并在稍后由一个由虚拟机自动建立的,低优先级的Finalizer线程去执行它。
这里的执行,指的是虚拟机会去触发finalize()方法,但不保证会等待它运行结束。
这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环,将会可能导致F-Queue队列中的其他对象永久处于等待,甚至导致整个内存回收系统崩溃。
所以,finalize()方法是对象逃脱死亡的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记。如果对象要在finalize()中成功拯救自己-只要重新与音乐链上的任何一个对象建立关联即可,比如自己(this关键字)赋值给某个类变量或者对象的成员变量,那么在第二次标记时它将会被移出“即将回收”的集合。如果对象这个时候还没有逃脱,那基本上它就真的被回收了。
/**
* 1.对象可以在被GC时自救
* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
*/
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes ,i am still alive:)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC();
//对象第一次拯救自己
SAVE_HOOK = null;
System.gc();
//因为finalize方法的优先级很低,所以暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead :(");
}
//下面的代码与上面完全相同,但却自救失败了
//因为任何对象的finalize()方法都只会被系统自动调用一次
//如果对象面临下一次回收,它的finalize()方法不会再次执行
SAVE_HOOK = null;
System.gc();
//因为finalize方法的优先级很低,所以暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
}else {
System.out.println("no,i am dead :(");
}
}
}
当然,并不鼓励用这种方法来拯救对象。相反,要尽量避免使用finalize()方法,因为finalize()方法不是C++中的析构函数,而是Java的一种妥协。finalize()方法的运行代价比较大,不确定性大,无法保证各个对象的调用顺序。
有些人,它比较适合“关闭外部资源”之类的工作。这是错的。用try-finally或者其他方法可以做得更好。
什么是引用
在JDK1.2之前,引用的定义就是,如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这个reference对象是一个引用。这样的说法比较狭隘。
在JDK1.2之后,将引用分为强引用,软引用,弱引用和虚引用。引用强度依次减弱。
强引用:只要强引用还在,垃圾收集器就不会回收掉被引用的对象;
软引用:还有用但非必须的对象。如果内存不够要发生内存溢出异常,就会将软引用关联的对象回收,如果内存还不够才会抛出内存溢出异常。
弱引用:非必需的对象。当垃圾回收器开始工作了,无论内存是否足够,都会回收被弱引用关联的对象。
虚引用:也称为幽灵引用或幻影引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得被关联的对象。虚引用的意义在于,在这个对象被回收的时候,收到一个系统通知。