Java对象的生与死

来源:互联网 发布:重命名文件夹 linux 编辑:程序博客网 时间:2024/04/29 18:09

Java对象的生与死

==============================

  我们都知道了Java虚拟机具有自动内存管理的机制,这个机制给Java程序员带来了许多的方便,减少了代码量。但是有时可能会出现一些用户不期望的错误,当你在程序中使用某个对象时,运行中提示这个对象已经死了,这时候当代码量比较大时排除这个错误就有点困难了。这就需要程序员在编程时还是要稍微了解一下Java对象在运行环境中是否存活。

判定存活算法

1、引用计数算法

  引用计数法是对每一个对象都加上一个引用计数器,当有程序有引用这个对象时,计数器的值就加一;当某个引用失效时,计数器的值就减一。任何时刻,当一个对象的计数器的值为0时,就表示这个对象不可能在被使用了,他就会被虚拟机回收掉。

  引用技术算法实现比较简单、执行效率也高,使用比较广泛。但是它至少还有一个问题,对于两个循环引用的对象,他们的程序计数器都的值至少都是1,如果通过引用计数算法回收的话他们是不能被回收的,如下代码:

public class ReferenceCountingGC {    public Object instance = null;    private static final int _1MB = 1024 * 1024;    /**     * 用来消耗内存,看是否会被GC回收掉     */    @SuppressWarnings("unused")    private byte[] bigSize = new byte[2 * _1MB];        public static void main(String[] args){        ReferenceCountingGC objA = new ReferenceCountingGC();        ReferenceCountingGC objB = new ReferenceCountingGC();        objA.instance = objB;        objB.instance = objA;        objA = null;        objB = null;        //执行GC方法,看objA和objB是否会被回收        System.gc();    }}

在程序中,对象objA和objB通过属性instance互相引用着对方,除此之外没有其他的引用了。因此他们的引用计数器的值都为1,通过引用技计数算法不能回收这两个对象。此时就需要用到下面的判定算法了。


2、可达性分析算法

  该算法是通过一系列的称为“GC Roots”的对象作为起点,然后从这些对象开始向下进行搜索,搜索走过的路径成为“引用链”,当一个对象不能通过各种引用链到达“GC Roots”时,就判定为这个对象可以被回收。如图:

图中Object5、Object6、Object7三个对象之间有引用,但是他们没有通过引用链和“GC Roots”相连,所以会被判定为可回收对象。

在Java中可以作为“GC Roots”的对象有如下几种:

   - 虚拟机栈中(栈中本地变量表)引用的对象

   - 方法区中类的静态属性引用的对象

   - 方法区中常量引用的对象

   - 本地方法栈中JNI(即本地Native方法)引用的对象

最终判定

  最后判定为“GC Roots”不可达的对象也并不是很快就将其回收掉了,还要通过一个“缓刑”的阶段。在这个过程中要通过两次标记:

   1、如果一个对象没有通过引用链和“GC Roots”相连,那么他将会进行第一次标记并且进行筛选,筛选的条件是该对象是否有必要执行finalize()方法,如果该对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过(finalize()方法只会被虚拟机自动调用一次),虚拟机将这两种情况视为“没有必要执行”。对于没有必要执行的对象虚拟机会将其回收。

   2、如果一个对象被判定为有必要执行finalize()方法,那么这个对象会被放到一个F-Queue的队列之中。此时finalize()方法有虚拟机自动建立的、低优先级的finalizer线程去执行它,这里的执行知识触发这个线程的执行,虚拟机并不会等待这个线程执行完毕,这样原因是如果一个对象在fianlize()方法中执行缓慢或者发生了死循环,这使得F-Queue队列中的其他对象处于永久等待状态并且还可能导致内存回收系统崩溃。在这次标记后,如果对象还是没有通过引用链与“GC Roots”相连,那就会被虚拟机回收。但如果在对象覆盖的finalize()方法中,对象通过将自己与引用链上的某个对象关联的话,它就又会被移出这个队列并且处于存活状态。如下代码中:SAVA_HOOK的自救过程

/** * @Title FinalizeEscapeGC.java * @Description 测试一个对象在被GC回收时的两次自救 *              但是自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次 * * @author zcy-fover * @Date 2016年8月15日 上午8:11:00 * @Version V1.0 */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. ");        //此句是对象自救的关键一步,将自己的指向与引用链的对象建立关联即可,        //否则就不能自救了,会在第一次执行System.gc方法时,就被回收        FinalizeEscapeGC.SAVE_HOOK = this;    }           public static void main(String[] args) throws InterruptedException {                    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: ");        }                   //与上一段代码完全相同,但是这次SAVE_HOOK对象自救失败了        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 method executed.     yes, I am still alive:      No, I am dead:

如果程序中没有加入FinalizeEscapeGC.SAVE_HOOK = this;这句代码时即对象没有进行“自救”,就会输出

     finalize method executed.     No, I am dead:      No, I am dead:

参考

  [周志明 ]. 深入理解java虚拟机-JVM高级特性与最佳实践(第二版)[M]. 北京:机械工业出版社, 2013.06

0 0
原创粉丝点击