【JVM】垃圾回收之对象已死

导读:本篇文章讲解 【JVM】垃圾回收之对象已死,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

本文参考:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)

1. 概述

总所周知,垃圾收集器的任务就是将“死去”的对象回收。

那么,垃圾收集器在对堆进行回收前,第一件事,就是要确认这些对象是否还“存活”着。

这里有两种算法:

  1. 引用计数算法
  2. 可达性分析算法

2. 引用计数算法

引用计数算法就是在对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器就加一;当引用失效的时候,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。因此垃圾收集器就可以将此对象回收。

垃圾收集器虽然需要占用额外的内存来进行计数,但是它的原理简单,判定效率也很高。不过Java虚拟机并没有选用引用计数算法来管理内存。

这是因为单纯的引用计数很难解决对象之间相互循环依赖的问题。

image-20230128181644604

比如有两个对象A和B,A对象引用B对象,B对象引用A对象,除此之外再无任何引用,但是实际上这两个对象不可能再被使用。如果使用引用计数算法,因为它们两个对象相互引用着对象,所以导致引用计数器都为1,因此垃圾收集器无法回收它们。


3. 可达性分析算法

可达性分析算法的思路就是通过一系列称为“GC Roots”的跟对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索的过程所走过的路径称为“引用链”

如果某对象到GC Roots间没有任何引用链相连,或者用图论的话来说从GC Roots到这个对象不可达,那么则证明此对象不可能再被使用

image-20230128191357640

图中object5object6object7虽然有关联,但是它们到GC Root不可达,因此可以判定为是可回收对象。

在Java中,固定可以作为GC Roots的对象包括以下几种

  1. 在虚拟机栈(栈帧中的本地变量表)中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  2. 在方法区中类静态属性引用的对象,比如Java类的引用类型静态变量。
  3. 在方法区中常量引用对象,比如StringTable里的引用
  4. 在本地方法栈中的JNI(也就是Native方法)引用的对象
  5. Java虚拟机内部的引用,比如基本数据类型对应的Class对象,一些异常对象(NullPointException等),还有系统类加载器。
  6. 所有被同步锁(synchronized关键字)持有的对象
  7. 反映Java虚拟机内部的情况JMXBeanJVMTI中注册的回调、本地代码缓存等

除了这些,还可以根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整的GC Roots


4. 引用的分类

在JDK1.2之前,如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。

在JDK1.2之后,引用分为强引用、软引用、弱引用和虚引用。

  1. 强引用
    1. 指的是程序代码中的引用赋值,比如Object obj = new Object(),只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  2. 软引用
    1. 软引用描述的是一些还有用,但非必须的对象
    2. 被软引用关联的对象,在系统将要发生内存溢出异常时(内存不住),触发垃圾回收。在垃圾回收之后,仍然内存不足,那么如果该对象没有被强引用,就会将这些对象进行回收。也就是第二次回收。
    3. JDK1.2之后提供了SoftReference实现软引用
    4. 可以配合队列来释放软引用本身
  3. 弱引用
    1. 弱引用也是用来描述那些非必须的对象
    2. 被弱引用关联的对象只能生存到下一次垃圾收集发生为止,无论内存是否足够,都会回收只被弱引用关联的对象。
    3. JDK1.2之后提供了WeakReference实现弱引用
    4. 可以配合队列来释放弱引用本身
  4. 虚引用
    1. 主要配合ByteBuffer使用,被引用对象回收时,会将虚引用入队,由Reference Handler线程调用虚引用相关方法释放直接内存。详情可看【JVM】详解直接内存_起名方面没有灵感的博客-CSDN博客
    2. JDK1.2之后提供了PhantomReference类来实现虚引用
  5. 终结器引用
    1. 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象

image-20230128204014313


4.1 软引用的应用

前提,添加VM Options——-Xmx20m -XX:+PrintGCDetails -verbose:gc

-XX:+PrintGCDetails -verbose:gc可以将垃圾回收的信息打印出来

private static final int _4MB = 10 * 1024 * 1024;
public static void main(String[] args) throws IOException {
    List<byte[]> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        list.add(new byte[_4MB]);
    }
    System.in.read();
}

执行上面代码的时候,很明显会出现堆内存空间不足,因此会抛出OutOfMemoryError

image-20230128205028210

但是改用软引用的话,就不会出现堆空间不足的情况了。

public static void soft() {
    // list --> SoftReference --> byte[]

    List<SoftReference<byte[]>> list = new ArrayList<>();
    for (int i = 0; i < 5; i++) {
        SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
        System.out.println(ref.get());
        list.add(ref);
        System.out.println(list.size());

    }
    System.out.println("循环结束:" + list.size());
    for (SoftReference<byte[]> ref : list) {
        System.out.println(ref.get());
    }
}

image-20230128205631791

看控制台不难看出,在内存不足触发垃圾回收的时候,并不会第一时间将软引用关联的对象回收,而是在第二次垃圾回收和内存仍然不足的情况下,才会将软引用的对象回收。

可以使用队列将软引用移除

private static final int _4MB = 4 * 1024 * 1024;

public static void main(String[] args) {
    List<SoftReference<byte[]>> list = new ArrayList<>();

    // 引用队列
    ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

    for (int i = 0; i < 5; i++) {
        // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
        SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
        System.out.println(ref.get());
        list.add(ref);
        System.out.println(list.size());
    }

    // 从队列中获取无用的 软引用对象,并移除
    Reference<? extends byte[]> poll = queue.poll();
    while( poll != null) {
        list.remove(poll);
        poll = queue.poll();
    }

    System.out.println("===========================");
    for (SoftReference<byte[]> reference : list) {
        System.out.println(reference.get());
    }

}

在这里插入图片描述


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94935.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!