zoukankan      html  css  js  c++  java
  • 对象的自我救赎

    一、如何判断对象已死?

      我们知道程序计数器、虚拟机栈、本地方法栈三个区域都是线程私有的,它们的生命周期都是随着线程而生,随着线程而亡,因此是不需要过多考虑回收的问题,而Java Heap(堆)Method Area(方法区)不一样,必须考虑内存回收问题。

    我们只有当程序运行的时候才会知道会创建哪些类,而这些类占用内存也是不同的,不会固定,都是动态的,即这部分的内存的分配与回收都是动态的,垃圾收集器关注的就是这部分内存

      VM判断对象是否死亡主要有两种方式,一种是通过计数器的方式,另外一种是通过引用链(根搜索算法):

    1、计数器方式

      这是很多教科书是提及到的方法,可以算是经典的一种方式。

      (1)原理:通过引用一个计数器,每次对象被引用就会加1,当引用失败后,自然减1...如此下去,直到计数器为0(其实任何时刻都可能为0),那么可以认为此对象不可能再被引用了,也就是达到GC的条件。

      (2)优点:简单容易实现,判断效率也高,计数器的操作并不占用过多资源。

      (3)缺点:那很明显就是无法解决对象之间的循环引用问题,至于什么是对象之间的循环引用,可以通过下面的代码理解:

        

    private Object instance = null;
    
    GCObject ojbA = new Object();
    GCObject ojbB = new Object();
    //对象之间的循环引用
    objA.instance = objB;
    objB.instance = objA;
    //两个对象已经不能被引用
    objA = null;
    objB = null;
    //这里开始GC
    system.gc();
    View Code

     

     2、根搜索(GC Roots Tracing)方式

      在很多主流的VM中都会使用到此方式,个人理解此方式主要是利用数据结构中“有向图”是否存在可达路径(其实也不太准确,但是说是Tree也是不准确的),不存在则说明引用链不存在,对象是死亡状态的。

      (1)原理:通过一系列可以被认为为根对象(GC Roots的对象作为起始点,从这些起始点往下搜索遍历,得到的遍历路径称之为引用链(Reference Chain,当一个对象的到GC Roots不存在任何引用链时(不可达),则说明该对象不可用,引用此对象的其他对象可能也会随之不可用。

     

          

        

        图中Object7与GC Root之间已不存在引用链,随之带来的Object8、Object9亦然。故做“死亡”对象被GC。

      (2)GC Roots对象:

      • Java Stack中本地变量表中引用的对象:Java栈本地变量表中引用类型指向Java堆中的对象,即Object object = new Object()中object引用Object对象
      • Method Area中类static静态属性引用的对象:即static object...方法区中存储类的信息、常量(final)、静态(static)变量等

      • Method Area中常量引用的对象:即final Object...

      • 本地方法栈中JNI的引用对象:即Native方法引用的对象

       

      (3)优点:可以避免对象循环引用问题,能彻底知道对象是否为死亡状态。

      (4)缺点:遍历需要时间,效率相对低。

    二、被死亡标记的对象不能有机会复活吗?

      经过根搜索算法后,那些不可达的对象也不是一定会要被执行“死刑”的,实际上只是暂时处于“缓刑”状态,GC会给它们再一次“复活”的机会。真正要执行“死刑”的对象都要经过两次标记过程

      (1)不可达的对象将会被第一次标记并进行一次筛选(此对象是否有必要执行finalize()方法),有两种情况时没必要的:对象没有覆盖finalize()方法,或者finalize()方法已经被VM调用过。

      (2)如果是有必要执行finalize()方法,那么该对象会被放入一个F-Quence队列中,然后VM自动建立一个低优先级的Finalizer线程与执行(触发finalize()方法),大不会等待它结束为止。这是因为防止对象在执行finalize()时死循环或者其他更严重的情况,那么F-Queue队列中其他对象都会一直等待。F-Quece队列中对象只要在执行finalize()方法过程中能够再次建立一个引用链,救能把自己“救活”,那么VM就会进行第二次标记将该已经“自我救赎”的对象移出F-Quence队列。反之如果没有任何的引用链,那么该对象的生命就到期结束。但这种“自我救赎”只能有一次机会,因为一个对象的finalize()方法只能被系统自动调用一次。注意:finalize()析构函数不像C++那样比用,在Java中是不推荐使用的,是因为运行成本高且不确定性大

        总结起来就是:VM会对那些执行了finalize()方法的对象利用F-Quence队列进行一次finalize()方法中是否存在存在引用链(自我救赎)的确认。有则存活(得感谢自己最后一次挣扎!),无则死亡(神仙都救不了你!)

      通过下面的代码实现来更加理解这个过程:

     1 /**
     2  * 模拟对象在生死边缘拯救自己的过程
     3  * @author Lijian
     4  */
     5 public class FinalizeEscapeGC {
     6     
     7     public static FinalizeEscapeGC SAVE_HOOK = null; //没有引用链,很危险很可能被回收
     8     public void isAlive() {
     9         System.out.println("I am still alive :)");
    10     }
    11 
    12         //重写finalize()方法:有必要执行finalize()方法,放入F-Quece队列
    13     //VM 创建的线程Finalizer会执行finalize()方法,这是对象唯一一次自我救赎机会
    14     @Override
    15     protected void finalize() throws Throwable {
    16         super.finalize();
    17         System.out.println("finalize method executed!");
    18         FinalizeEscapeGC.SAVE_HOOK = this;//真正的自我救赎,重新在finalize()中建立引用链
    19     }
    20   public static void main(String[] args) throws Throwable{
    21         SAVE_HOOK = new FinalizeEscapeGC();
    22         
    23         //第一次救赎
    24         SAVE_HOOK = null;//不存在引用链,引起GC注意将会第一次标记
    25         System.gc();
    26         Thread.sleep(500);//Finalzier方法优先级低,所以先暂定0.5m。等待它
    27         if (SAVE_HOOK != null) {
    28             SAVE_HOOK.isAlive();//还活着    
    29         }else {
    30             System.out.println("No, I am dead :(");
    31         }
    32         //不等于null证明活过来了
    33         System.out.println(SAVE_HOOK);
    34         
    35         //再一次救赎:活过来之后再失去引用链,再次尝试自我拯救
    36         //但是finalize()方法只能被系统调用一次,结果是失败的
    37         SAVE_HOOK = null;//活过来之后再失去引用链
    38         System.gc();
    39         Thread.sleep(500);
    40         if (SAVE_HOOK != null) {
    41             SAVE_HOOK.isAlive();
    42         }else {
    43             System.out.println("No, I am dead!");
    44         }
    45     }
    46     
    47 }    

    输出结果:

    1 finalize method executed!
    2 I am still alive :)
    3 main.FinalizeEscapeGC@7852e922
    4 No, I am dead!

    整个流程如下:

     接下来就是垃圾回收算法、垃圾回收器、内存分配与回收等。下一步将会做进一步介绍!

  • 相关阅读:
    python: 字符串按空格分成列表split与加密密码maketrans
    android 几个工具方法
    华硕N43sl VNP 连接问题 800 807 621
    git 创建新项目,下载工程,合并和更新工程简单应用记录
    android分析windowManager、window、viewGroup之间关系(二)
    android分析windowManager、window、viewGroup之间关系(一)
    meta-data获取小结
    转载: 项目经验分享
    不想写代码了
    MapReduce源码分析总结(转)
  • 原文地址:https://www.cnblogs.com/jian0110/p/9339000.html
Copyright © 2011-2022 走看看