Java如何决定对象的生死及对象该如何逃脱?

垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象中哪些还“存活”,哪些已经“死去”。

引用的分类

Java将引用分为4种:

  • 强引用:指在程序代码之中普遍存在的引用赋值,即类似 “Object obj = new Object ()”
    这种引用关系。无论何种情况下,只要强引用关系存在,垃圾收集器就不会回收调被引用的对象。

  • 软引用:用来描述一些还有用,但非必须的对象。只要被软引用关联着的对象,在系统将要发生内存溢出异常之前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。

  • 弱引用:也是用来描述那些非必须对象,但它的强度比软引用还弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生为止

  • 虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象实例。

引用计数法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加一;当引用失效时,计数器值就减一;计数器为零的对象就是不可能再被使用的。
然而,在Java中,并没有选择引用计数法来进行内存管理。

可达性分析算法

可达性分析算法的基本思路就是通过一系列称为“GC Roots” 的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索走过的路径称为“引用链”。如果某个对象到GC Roots 之间没有任何引用链,则证明该对象是不可能被使用的。

可达性分析算法
可作为GC Roots的对象如下:

  • 在虚拟机栈中引用的对象,比如各个线程被调用方法堆栈中的参数、局部变量、临时变量等
  • 在方法区中类静态属性引用的对象,比如引用类型静态变量
  • 在方法区中常量引用的对象,比如字符串常量池里的引用
  • 在本地方法栈中JNI(Native)引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,常驻的异常对象
  • 所有被同步锁(synchronized)持有的对象
  • 反映虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等

除了以上外,也可以"临时"加入一些对象作为GC Roots对象。(比如在新生代回收时,就需要考虑老年代对象的调用,需要将关联区域的对象也一并加入到GC Roots中)

死亡逃脱

先看以下代码

/**
 * 1.对象可以在被GC时自我拯救
 * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("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();
        // 因为finalizer 方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("I am dead!");
        }

        //下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        // 因为finalizer 方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("I am dead!");
        }

    }
}

执行结果:
在这里插入图片描述
这是为什么呢?
第一次成功自救是因为真正对一个对象回收,需要进行两次标记
如果对象在进行可达性分析后,发现没有引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是是否有必要执行finalizer 方法,假如对象没有没有覆盖finalizer ()方法,或者finalizer ()已经被调用过,那虚拟机将这两种情况视为没必要执行。第二次不能逃脱,是因为系统已经执行过finalizer方法了
如果对象被判断为有必要执行finalizer()方法,那么该对象会被放进一个低优先级的队列F-Queue中,稍后收集器将对F-Queue中的对象进行第二次小规模的标记。第一次逃脱就是因为在finalizer方法中重新建立了关联,所以第二次标记时它被移出"即将回收" 的集合

finalizer方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,已经被官方声明为不推荐使用的语法。笔者建议大家使用try-finally方式

回收方法区

方法区的垃圾收集主要回收两部分内容: 废弃的常量和不再使用的类型

参考:《深入理解Java虚拟机》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值