您的位置:首页 > 编程语言 > Java开发

3.检测对象是否可以回收及Java的引用

2018-03-31 22:03 295 查看

确定对象是否可以回收了

       在堆里面存放了几乎所有的对象实例,垃圾收集器在对堆进行来及回收之前,需要确定哪些对象可以被回收了(即,不可能再被任何途径使用了)。

引用计数算法

      给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加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之后,将引用分为强引用,软引用,弱引用和虚引用。引用强度依次减弱。
       强引用:只要强引用还在,垃圾收集器就不会回收掉被引用的对象;
       软引用:还有用但非必须的对象。如果内存不够要发生内存溢出异常,就会将软引用关联的对象回收,如果内存还不够才会抛出内存溢出异常。
       弱引用:非必需的对象。当垃圾回收器开始工作了,无论内存是否足够,都会回收被弱引用关联的对象。
       虚引用:也称为幽灵引用或幻影引用,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得被关联的对象。虚引用的意义在于,在这个对象被回收的时候,收到一个系统通知。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息