JVM之判断对象生死【二】

在上篇文章中总结了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈这三个区域和线程生命周期相同,虚拟机栈中的栈帧随方法的执行而进行相应的出栈和入栈操作,每一个栈帧中分配多少内存基本在类结构确定下来时就已知了。所以这几个区域的内存分配和GC都具备确定性,随着方法或线程结束,内存也就自然回收了。而堆和方法区不一样,一个接口中多个实现类需要的内存可能不一样,一个方法中多个分支需要的内存也可能不一样,只有程序运行时才能确定这部分的内存分配,垃圾收集器关注的就是这部分内存区域。因为对象都是存放在堆中,在进行GC时要确定哪些对象可以进行回收。
在这里插入图片描述

首先明确一个观点: 对象不是非生即死的。当空间足够,某些对象可以保留;当空间不足,某些对象才会被当成垃圾进行回收。

1. 判断对象生死状态的算法

1.1 引用计数器算法

在对象的对象头分配一块内存空间存储该对象被引用次数,每当这个对象被引用的时候+1,与之相反,每当一个引用失效就-1,当为0时就视为可回收对象。

优 点:实现简单,性能高。
缺 点:重要的是无法解决循环引用的问题
在这里插入图片描述

1.2 可达性分析算法

java(包括主流的变成语言)就是采用可达性分析算法来确定对象生死状态的。

该算法通过一系列“GC Roots”对象作为起始点,从这些对象往下搜索,所经过的路径为“引用链”,如果一个对象到GC Roots没有任何引用链相连,那么这个对象就是可以回收的。
在这里插入图片描述
可作为GC Roots 对象的有如下四种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 本地方法栈中Native方法引用的对象
  3. 方法区中常量引用的对象
  4. 方法区中类静态属性引用的对象

2. 对象生死和引用的关系

不管是引用计数器算法还是可达性分析算法都和对象引用有关,所以对象的引用决定了对象生死。
java中的对象引用分为四种:1.强引用、 2.软引用、 3.弱引用、 4.虚引用,引用强度按顺序递减。

1. 强引用:Object obj = new Object(); 在代码中最常见的,只要强引用还在,垃圾收集器永远不会回收 引用的对象,建议使用完对象,手动置为null, obj = null, 防止内存泄漏。

2. 软引用:jvm 在 OOM之前,会尝试回收软引用指向的对象。

3. 弱引用:jvm在发生GC时回收软件用指向的对象。

4. 虚引用:无法通过虚引用来获取对象实例,为对象设置虚引用的唯一作用:当引用的对象被垃圾收集器回收时收到一条系统通知。

3. 死亡标记与拯救

在某个对象进行可达性分析之后,真正死亡要经历两次标记过程。

如果该对象到达GC Roots没有引用链相连,会被第一次标记,然后进行筛选,筛选条件有两个:
1.该对象重写了Object 的finalize();
2.对象的finalize()没有被执行过(对象的finalize()只能执行一次)。

不满足筛选条件的进行第二次标记也就是死亡了,满足筛选条件的会将该对象加到一个队列中,稍后由一个虚拟机创建的低优先级的线程执行。

如果在finalize()中重新建立了和引用链上任何一个对象的关联,这个对象不会死亡了。

public class FinalizeTest {
    private static FinalizeTest finalizeTest = null;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize方法执行了");
        FinalizeTest.finalizeTest = this;
    }

    public static void main(String[] args) throws InterruptedException {
        finalizeTest = new FinalizeTest();

        //对象第一次成功拯救自己
        finalizeTest = null;
        System.gc();
        //因为finalize方法优先级很低,所以暂停1秒等待一下
        Thread.sleep(1000);
        if(finalizeTest!=null){
            System.out.println("对象被拯救了");
        }else{
            System.out.println("对象死亡了");
        }

        //这段与上面完全相同,但是却拯救失败了
        finalizeTest = null;
        System.gc();
        Thread.sleep(1000);
        if(finalizeTest!=null){
            System.out.println("对象被拯救了");
        }else{
            System.out.println("对象死亡了");
        }
    }
}

执行结果如下:
在这里插入图片描述

不建议使用finalize()拯救对象,原因如下:
1.只能执行一次
2.运行代价高昂
3.无法保证各个对象的调用顺序

发布了11 篇原创文章 · 获赞 0 · 访问量 615

猜你喜欢

转载自blog.csdn.net/fei1234456/article/details/104733165
今日推荐