GC-谈谈“生死”

引言:

今天周六,技术博客不会断更。今天想和大家聊聊“生死”。有研读过jvm的小伙伴们知道java相比于c++来说,内存动态分配垃圾回收技术是两大核心。一个对象的出生由我们来创造,但是对象的死亡很多时候并不由我们决定,而是由垃圾回收技术进行管理和操作。在“技术点”我会简单介绍一下jvm的组成结构和它们与线程的关系,方便我们更好理解。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290

技术点:

1、jvm运行时数据区组成结构

方法区、虚拟机栈、本地方法栈、堆和程序计数器。下面是我从百度找的图片:

这里写图片描述

对于每一个模块的作用,笔者就不再展开了,如果需要的话,我会在后面可以专门挑一天写一个博文,专门介绍每个部分的作用和运行状态。话虽如此,知识本来就是比较发散的东西,我就说几点我面试过程中遇到最多问题:①对象实例存在哪?存在堆中。②对象引用存在哪啊?存在虚拟机栈(注意:我们平时说的栈就是虚拟机栈)。③已加载的常量,静态变量,编译后的代码存在哪啊?存在方法区。ok,点到即止,如果大家很想深入了解这块的话,可以联系我。回归正题,从上图中可以看出:方法区和堆是线程共享的,而虚拟机栈,本地方法栈和程序计数器是线程隔离的。

2、引用的概念

很早以前的java中,引用的定义是:虚拟机栈中的本地变量表中存储的数值代表的是另一块内存的地址,就称另一块块内存被引用。但是现在区分远比以前要严谨的多,主要分为以下四种引用类型:①强引用;②软引用;③弱引用;④虚引用。强引用就是永远不会回收掉被引用的对象,比如说我们代码中new出来的对象。软引用表示有用但是非必需的,如果系统内存资源紧张,可能就会被回收;弱引用表示非必需的对象,只能存活到下一次垃圾回收发生之前;虚引用是最弱的,这个引用无法操作对象。

如果引用不存在了,就表示对象死亡了。

控制哪里的“生死”?

从技术点1我们可以看出,虚拟机栈,程序计数器和本地方法栈都是线程隔离的,随着线程而生,随着线程而死,考虑它的死活并没有什么意义。而堆和方法区是线程共享的,程序运行之后,我们才知道创建了多少对象,所以在这部分中内存是动态分配的,所以这部分才是“生死”的关键区域。

如何判断“生死”?

gc机制作为对象的“生死”的“收尸者”,在工作之前需要先准确判断好某个对象是否还存活,不然就会出大事了。在这里主要会有两种常见的办法进行判断:

1、引用计数法:每个对象都会被配备一个引用计数器,每当有一个地方引用它时,计数器就+1;引用失效时,计数器就-1。当引用计数器为0的时候就表示对象已经死亡。但是这个办法有一个很严重的bug,如果两个对象相互引用咋办?假如两个对象之存在相互引用,除此之外没有其余的引用,那么这两个对象已经死亡了。如果用引用计数法就会无法回收。所以目前jdk版本是不会使用这种方式判断。

2、可达性分析算法:找寻特定点(GC Roots)作为根节点(一般来说不止存在一个根节点的),从这些节点的引用关系向下搜索,会组成一条链子一样的东西,这个东西我们称为引用链。如果某一个对象不存在任何一条引用链上就表示这个对象已经死亡了。

置于死地而后生

被判定死亡的对象真的就非死不可了么?其实不然的。这里为在说一个面试经常会问到的问题,用以引出为什么会置于死地而后生。请简述:final,finally,finalize的区别?final是java关键字,如果用于修饰类,表示这个类不可以被继承,比如说String类。如果用于修饰变量,那么变量在使用过程中就不能被修改,比如我们项目中都会有一个commconstent类吧。finally是存在于try-catch结构之中,表示不管try-catch怎么运行都会执行finally中的代码。

好啦,finalize就是我们今天要说的能置对象死地而后生的东西。如果在可达性分析算法过程中,发现某一个对象与引用链并不关联,那么他就会去判断这个对象中有没有覆盖finalize()方法,如果覆盖了该方法,该对象就会放置到一个F-Queue的队列之中。后面就会执行它(执行并不保证会执行完,因为finalize方法过于繁琐或者执行中出了错误,为了不让队列中其他对象处于永久等待会中止执行)。

也就是说finalize()方法是对象的最后一根“救命稻草”,也是“后生”的关键,错过这个机会,基本上就拜拜了。而且,更宁这个生死边缘的对象难过的是,如果靠finalize()方法救活一次,第二次又要进入回收了,finalize()方法就不会再次救它了。这里还要说明一下,对象“救活”成功需要在finalize()方法中,把自己关联到引用链上,关联不上也是要面临死亡回收。所以,finalize()方法在日常开发中能不用尽量别用,很容易就脱离你的掌控。

下面就是finalize()方法就活对象的demo:

package com.bw;

public class GcTest {

    public static GcTest gcTest = null;

    @Override
    protected void finalize() throws Throwable {//重写finalize方法
        super.finalize();
        System.out.println("执行finalize方法");
        GcTest.gcTest = this;//把自己关联到引用链上
    }

    public static void main(String[] args) throws InterruptedException {
        gcTest = new GcTest();//创建一个悲剧对象

        //第一次尝试救活
        gcTest = null;
        System.gc();
        Thread.sleep(500);//finalize()方法优先级很低,需要等待一下它
        if(gcTest != null){
            System.out.println("第一次被救活");
        }else {
            System.out.println("第一次救活失败");
        }

        //对象救活后又失去了和引用链到关联,也就是进入第二次尝试救活

        gcTest = null;
        System.gc();
        Thread.sleep(500);
        if(gcTest != null){
            System.out.println("第二次被救活");
        }else {
            System.out.println("第二次救活失败");
        }

    }
}


//输出:执行finalize方法
        第一次被救活
       第二次救活失败

从demo中可以发现,第二次的时候,finalize救都懒的救了。所以呀,人生中也一样,很多时候,机会对于你来说,可能也只能存在一次,所以好好珍惜生活中的每一次机遇。

如果博文存在什么问题,或者有什么想法,可以联系我呀,下面是我的微信二维码:
这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章