0
点赞
收藏
分享

微信扫一扫

Finalize进程以及Object.finalize的作用

WikongGuan 2022-01-13 阅读 59

Finalize进程以及Object.finalize的作用

一. finalize()有什么作用

有过Java虚拟机基础的读者们知道,Java程序中的每个对象都有属于自己的生命周期,一旦某个对象进入死亡状态,那么这个对象就会被GC回收掉。那么如何判断一个对象是死亡的呢?

1.1 对象是否死亡

  1. 引用计数法。
  2. 可达性分析法。
  3. 再谈引用(强、软、弱。虚引用)。
  4. finalize()方法。

我们知道Object类是所有类的父类,而 finalize()方法就是Object类下的方法,并且没有具体的实现。那么在这里直接说 finalize()方法的作用:

  1. 若某个类实现了此方法并重写(且没有被执行过),并能够成功的让此对象重新与引用链上的任何一个对象关联(即可达)。
  2. 那么这个对象就能够逃脱GC,逃脱被回收的命运。

备注:

1.2 finalize()实战

Demo如下(引自我的另一篇文章深入理解Java虚拟机系列(二)–垃圾收集器与内存分配策略):

public class FinalizeTest {
    public static FinalizeTest INSTANCE = null;

    public void isAlive() {
        System.out.println("--------我存活了---------");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("----------finalize方法执行成功!-------");
        FinalizeTest.INSTANCE = this;
    }


    public static void main(String[] args) throws InterruptedException {
        INSTANCE = new FinalizeTest();
        INSTANCE = null;
        System.gc();
        // 因为finalize方法的优先级很低,所以暂停0.5s等待他。
        // 对象第一次去尝试拯救自己
        Thread.sleep(500);
        if (INSTANCE != null) {
            INSTANCE.isAlive();
        } else {
            System.out.println("----------我已经死亡(第一次)----------");
        }

        INSTANCE = null;
        System.gc();
        // 因为finalize方法的优先级很低,所以暂停0.5s等待他。
        // 对象第二次去尝试拯救自己
        Thread.sleep(500);
        if (INSTANCE != null) {
            INSTANCE.isAlive();
        } else {
            System.out.println("----------我已经死亡(第二次)----------");
        }
    }
}

程序运行结果如下:
在这里插入图片描述
可以佐证:

  1. finalize()方法确实在GC的时候被触发了一次,并成功的让对象可达,逃脱被GC
  2. finalize()方法只能够执行一次,所以第二次无法逃脱被GC

那如果不重写 finalize()方法,看看结果是什么?(自己把代码注释掉)
在这里插入图片描述

可以见到,必须要重写父类Object类的 finalize()方法,才能够在GC的时候,有机会去逃脱回收。

二. Finalizer线程

2.1 Demo1

Finalizer线程相关介绍:

  1. 若对象重写了finalize()方法,那么该方法会由Finalizer线程来执行。
  2. Finalizer线程的优先级较低。
  3. Finalizer线程虽然会触发finalize()方法,但是并不会保证会等待它运行结束。

给大家看个案例:
相关的VM参数设置:

-verbose:ge -XX:+PrintGCDetails -Xms64m -Xmx64m -Xmn64m 

Demo

public class Test {
    static AtomicInteger aliveCount = new AtomicInteger(0);

    Test() {
        aliveCount.incrementAndGet();
    }

    @Override
    protected void finalize() throws Throwable {
        Test.aliveCount.decrementAndGet();
    }

    public static void main(String[] args) {
        int i = 0;
        while (true) {
            Test test = new Test();
            if ((i++ % 100_000) == 0) {
                System.out.format("After creating %d objects, %d are still alive.%n", new Object[]{i, Test.aliveCount.get()});
            }
        }
    }
}

运行结果如下:
在这里插入图片描述
若加上GC分析日志,部分GC结果如下:
在这里插入图片描述

代码分析如下:

  1. 首先程序的本质是不断创建Test类,并且创建出来的Test对象,我们并没有做什么事情,我们理想的状态是这个Test类会被立马GC回收掉。
  2. 我们创建一个对象,计时器+1,对象经过一次GC(就是会调用一次finalize()),计时器-1。
  3. 从结果可以发现,我们可以发现创建出来的对象并不是我们理想状态中的被立马回收,并且最终抛了OOM异常。

2.2 Demo2

同时,可以尝试将finalize()方法删除,并将VM的堆大小限制给去除,那么程序放一段时间后,又是另一番景象:
在这里插入图片描述
从日志输出可以发现,后期一直在进行Full GC,对老年代和持久代进行垃圾回收。实际上,当我们重写了finalize()方法后,会为每一个Finalizable对象创建一个实例,并为Finalizable类所引用,那么由于该引用链,导致整个Test对象都是存活的。

那么在不断创建Test对象的过程当中,Eden区的内存也会被不断的占用,因此对象从Eden区最终转移到老年代里面,老年代堆满了之后,就会进行Full GC

在代码执行过程中,在终端使用jps命令查看线程
在这里插入图片描述
随后使用jstack命令来查看相关线程信息:

jstack 8100

可得:代码执行过程中有个Finalizer守护线程正在运行。
在这里插入图片描述
在程序启动过程中,可以使用Arthas来观察相关的参数:
在这里插入图片描述

代码分析:

此时此刻有两个线程在不停的循环:

  • Main主线程:负责创建Test对象,由于Test类重写了finalize()方法,因此每个Test的子类都会有个Finalizer对象创建出来,会放到F-Q队列里面。(java.lang.ref.Finalizer.ReferenceQueue
  • Finalizer守护线程:负责处理F-Q队列,将对象弹出,并调用其finalize()方法。

但是。由于 Finalizer守护线程的优先级比较低,因此其处理速度无法跟的上主线程的创建对象的速度。导致堆中可用的空间被耗尽。

举报

相关推荐

0 条评论