JVM垃圾回收-finalization机制& 安全点& 安全区域(二)

finalization机制概述

  • 当垃圾回收时对象被销毁前, 总会先调用对象的 finalize()方法(被调用的前提是必须重写此方法, 同时未被调用过, 因为此方法只会调用一次), 常用于处理资源释放 如关闭文件, 套接字, 数据库连接等

对象的3种状态

  • 当一个对象与 GC Roots失去联系时, 就意味着对象已经不再使用了. 通常进入垃圾回收时就会被回收, 但是如果重写了 finalize()方法, 虚拟机不会立马回收, 而调用 finalize()方法缓一次回收(也就是给了一次复活的机会)
  1. 可触及的: 可达对象(与 GC Roots直接或间接的联系着)
  2. 可复活的: 不可达对象(失去了与 GC Roots的联系), 但有一次执行 finalize()方法缓一次回收的机会(此时可以编写复活相关逻辑)
  3. 不可触及的: 已调用过一次 finalize()方法, 且没有复活. 此时的状态就是不可触及的状态. 此时进入回收, 将肯定会被回收

销毁对象之前具体判断过程

  • 判定一个对象A是否可回收, 会经过两次标记过程:
  1. 如果对象A与 GC Roots失去联系, 则进行第一次标记
  2. 是否执行 finalize()方法的判断过程:
    (1) 对象A重写了 finalize()方法(如没有重写意味着没有复活的过程), 同时 finalize()方法已调用过一次, 则判定对象A为不可触及的状态
    (2) 对象A重写了 finalize()方法, 且还未执行过, 那么对象A会被插入到一种 F-Queue队列(引用队列)中, 由一个虚拟机自动创建的, 低优先级的 finalizer线程触发其 finalize()方法执行
    (3) - 稍后(finalizer线程的触发后), 将会对 F-Queue队列中的对象进行第二次标记. 此时如果对象A在 finalize()中复活了(也就是重新建立了, 与 GC Roots的联系), 那么在此次标记时, 对象A会从’即将回收’的集合中移出. 之后, 如果对象A再次出现与 GC Roots失去联系的情况, 会直接成为不可触及的状态, 因为 finalize()方法只会被调用一次

演示例子


public class FinalizeTestApp {
    /** GC Roots*/
    public static FinalizeTestApp objA;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("invoking `finalize` by GC");
        /** 赋予引用地址, 目的为复活*/
        objA = this;
    }

    public static void main(String[] args) {
        try {
            objA = new FinalizeTestApp();
            /** 删除引用地址*/
            objA = null;
            /** System.gc()或 Runtime.getRuntime().gc(); 会显式的触发 Full GC
             * 即使直接调用 以上方法, 也无法保证对垃圾收集器的调用
             * */
            System.gc();
            System.out.println("GC 1");
            /** 由于 Finalizer线程优先级较低, 暂停2秒, 为了保证 GC的执行*/
            Thread.sleep(2000);
            if (objA == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }

            /** 重复了上面代码, 为了演示 finalize()的被调用次数*/
            objA = null;
            System.gc();
            System.out.println("GC 2");
            /** 由于 Finalizer线程优先级较低, 暂停2秒, 为了保证 GC的执行*/
            Thread.sleep(2000);
            if (objA == null) {
                System.out.println("obj is dead");
            } else {
                System.out.println("obj is still alive");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

输出:
> GC 1
> invoking `finalize` by GC
> obj is still alive
> GC 2
> obj is dead

  • 注: 永远不要主动调用对象的 finalize()方法, 应由垃圾回收机制自动调用, 理由3点:
  1. 在 finalize()时可能会导致对象复活
  2. finalize()方法的执行时间是没有保障的, 它完全由 GC线程决定, 极端情况下, 若不发生GC, 则 finalize()方法将没有执行机会
  3. 一个糟糕的 finalize()会严重影响 GC的性能(重写的情况)
  • finalize()方法区别于 C++的析构函数, 虽然比较相似, 但 Java是基于垃圾收集器的自动内存管理机制, 所以本质上不同于 C++的析构函数

安全点与安全区域

安全点(Safepoint)

  • 程序执行时并非在所有地方都能停顿下来开始 GC, 只有在特定的位置才能停顿下来进行 GC, 这些点成为安全点
  • 当 JVM要触发 GC, 偏向锁解除等操作时, 所有的用户线程都必须到达安全点

安全区域(Safe Region)

  • 安全点保证了程序执行过程中的时间隔比较近的安全点. 但是有些线是处于睡眠/阻塞/等待执行的状态(此时线程是无法响应 JVM的中断请求的), 此时就会用到安全区域
  • 安全区域是指在一段代码片段中, 对象的引用关系不会发生变化, 在这个区域中的任何位置开始 GC都是安全的
  • 当线程运行到安全区域的代码片段时, 首先会标识已经进入了安全区域, 如果这段时间内发生 GC, JVM会忽略标识为安全区域的线程
  • 当线程即将离开安全区域时, JVM会检查是否已经完成 GC, 如果完成了, 则继续运行, 否则线程必须等待直到收到可以安全离开安全区域的信号为止

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

猜你喜欢

转载自blog.csdn.net/qcl108/article/details/108835348