JVM -四种引用-垃圾回收(1)

12 篇文章 0 订阅
4 篇文章 0 订阅

在说引用之前我们,常会判断一个对象是否已死,这个会涉及到两种算法,以至于会影响虚拟机的垃圾回收的方式和垃圾回收器的应用。

如何判断一个对象已死?

两种算法:引用计数法,可达性分析算法

  • 应用计数算法 : 在一个对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器的值就加一; 当引用失效的时候,计数器的值就减一;任何时刻计数器为零的对象就不能再次使用的。

但是这个算法有个弊端,就是当对象出相互引用的时候,可能会导致两个对象的引用都不为零,无法回收。

  • 可达性分析算法 :通过一系列的“GC Roots” 的根对象作为起始节点集,从这些节点向下搜索,某个对象到“GC Roots” 之间没有任何引用链相连,或者说是从GC Roots 到这个对象不可达的时候,则证明这个对象是不可能再被使用的。

可作为GC Roots 对象有哪些?

  • java虚拟机栈(栈帧中的本地变量表)中的引用的对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI本地方法的引用对象。
  • 所有被同步锁 synchronized 所持有的对象。

举一个例子 List list = new ArrayList() ; 这个语句中 list 这个局部变量在 栈中,而这个对象在堆中,而 作为GC roots 的指的是栈中list 这个引用对象指的是 Arraylist 对象。

四种引用

Java中对引用 的概念进行看扩充,将引用分为强引用,软引用,弱引用,虚引用四种,四种由强到弱。

强引用(Strongly Reference)

  1. 普通变量赋值即为强引用,如 A a = new A();

  2. 通过 GC Root 的引用链,如果强引用不到该对象,该对象才能被回收

  3. 只要引用关系还在,永远不可能回收。
    在这里插入图片描述

软引用(Soft Reference)

  1. 例如:SoftReference a = new SoftReference(new A());

  2. 如果仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象

  3. 软引用自身需要配合引用队列来释放

  4. 典型例子是反射数据
    在这里插入图片描述

弱引用(Weak Reference)

  1. 例如:WeakReference a = new WeakReference(new A());

  2. 如果仅有弱引用引用该对象时,只要发生垃圾回收,就会释放该对象

  3. 弱引用自身需要配合引用队列来释放

  4. 典型例子是 ThreadLocalMap 中的 Entry 对象
    在这里插入图片描述
    查看弱引用的源码发现,构造方法有两种,一种是只传入对象的,创建弱引用将对象关联,另外一种是可以传入一个引用队列,当引用对象被回收的时候就会将引用对象,放入引用队列中,就会对被回收的对象进行一种标记,在引用对象中自己实现清除方法,将引用队列中的对象关联的对象进行清除,当gc 后可以,可以进行清除

public class WeakReference<T> extends Reference<T> {

    public WeakReference(T referent) {
        super(referent);
    }
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

ThreadlocalMap 就会是类似的情况,其中的内部节点类 Entry 继承了WeakReference 在创建的对象的时候键是作为弱引用,而值是强引用。所以容易出现内存泄漏。

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

所以当我们需要防止内存泄漏的时候,可以将这个引用队列传入,另外再写一个clean 方法将这个引用队列中的对象进行清除。

 static ReferenceQueue<Object> queue = new ReferenceQueue<>();
 static class Entry extends WeakReference<String> {
       String value;
       public Entry(String key, String value) {
                super(key, queue);
                this.value = value;
        }
  }
  public void clean() {
     Object ref;
     while ((ref = queue.poll()) != null) {
         System.out.println(ref);
         for (int i = 0; i < table.length; i++) {
            if(table[i] == ref) {
                 table[i] = null;
             }
         }
     }
  }
  Entry[] table = new Entry[4];

在jkd 中有一个 WeakHashMap 类其中节点就是实现了 WeakReaference

  • WeakHashMap 用来存储图片信息,可以在内存不足的时候,及时回收,避免了 OOM

虚引用(Phantom Reference)

  1. 例如: PhantomReference a = new PhantomReference(new A(), referenceQueue);

  2. 必须配合引用队列一起使用,当虚引用所引用的对象被回收时,由 Reference Handler 线程将虚引用对象入队,这样就可以知道哪些对象被回收,从而对它们关联的资源做进一步处理

  3. 典型例子是 Cleaner 释放 DirectByteBuffer 关联的直接内存

在这里插入图片描述
我们查看PhantomReference 的源码发现构造方法中必须传入一个引用队列,如果使用虚引用创建的对象,会在垃圾回收的时候,同时将引用的对象也会放入一个引用队列中,就会知道这个队列中引用的资源被回收,可以做一些进一步的处理,对一些这个对象所引用的外部资源(非堆内存的对象)进行清除。

public class PhantomReference<T> extends Reference<T> {

    public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

但是值得注意的一个点当我们注意一些,gc 只会清除堆中的垃圾和对象,不会去清除常量池中的对象,比如第二个对象被存在常量池中,在直接内存内,gc 不会清除,同样的虚引用对象也不会加入引用队列。

list.add(new PhantomReference<String>(new String("Hello"),queue));
list.add(new PhantomReference<String>("Hello",queue));

Cleaner 的使用

其实查看前几种的引用的作用都是配合引用队列对一些资源进行清理,可能是外部内存(非堆内存)的资源清除,或者是一些关联强引用资源对象的清除。而在jkd 9 之后出现了一个类 Cleaner 类进行这些动作,对这个进行了封装,我们只需要传入弱引用的对象和需要 清理的动作,后台会自动创建一个线程当弱引用被回收的时候,就会触发清除的动作。但是这个线程是守护线程,在主线程停止之后也会随之停止。

// 传入的是对象和 Runnable 任务
public Cleanable register(Object obj, Runnable action) {
        Objects.requireNonNull(obj, "obj");
        Objects.requireNonNull(action, "action");
        return new CleanerImpl.PhantomCleanableRef(obj, this, action);
}

在jdk 底层也会有这个类,但不是同一个包下,实现基本相同,在nio 那种使用直接内存的地方会出现清除的动作。

finalize() 方法的详解

  • 它是 Object 中的一个方法,如果子类重写它,垃圾回收时此方法会被调用,可以在其中进行资源释放和清理工作
  • 将资源释放和清理放在 finalize 方法中非常不好,非常影响性能,严重时甚至会引起 OOM,从 Java9 开始就被标注为 @Deprecated,不建议被使用了

如何使用这个方法 ?

  • 如果你没有重写这个方面,默认的会自己也不会去调用它,当这个对象不被引用的时候就会被释放,这个动作就是常见的 GC 由虚拟机自己完成 gc 。

这里特别的说明 System.gc () 这个方法时表示用来垃圾回收的,会显式触发 Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。但是不能保证立刻进行回收,虚拟机中也有一个参数可以禁止显示调用的-XX:-+DisableExplicitGC ,这个方法的显示调用就不会生效。

  • 如果你对这个finalize 方法进行了重写,这个就和之前大不相同了,在创建对象的时候,会将这个对象包装成一个Finailzer 对象,当这个对象不被引用的时候,需要被回收的时候,对象不会被回收,而会将其加入 一个 ReferenceQueue 的一个引用队列中,头结点是 unfinalized , 会有一个优先级表低的线程来依次执行这个对象的 finalize 方法,在执行方法前会将这个对象从 ReferenceQueue 队列中断开,之后当垃圾回收的时候就可以对其进行回收。(若是这个对象不想被回收就可以在方法中重新恢复一个引用。)

原理步骤:

  1. 对 finalize 方法进行处理的核心逻辑位于 java.lang.ref.Finalizer 类中,它包含了名为 unfinalized 的静态变量(双向链表结构),Finalizer 也可被视为另一种引用对象(地位与软、弱、虚相当,只是不对外,无法直接使用)
  2. 当重写了 finalize 方法的对象,在构造方法调用之时,JVM 都会将其包装成一个 Finalizer 对象,并加入 unfinalized 链表中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rYwmAXop-1636598591229)(img/image-20210901121032813.png)]

  1. Finalizer 类中还有另一个重要的静态变量,即 ReferenceQueue 引用队列,刚开始它是空的。当狗对象可以被当作垃圾回收时,就会把这些狗对象对应的 Finalizer 对象加入此引用队列
  2. 但此时 Dog 对象还没法被立刻回收,因为 unfinalized -> Finalizer 这一引用链还在引用它嘛,为的是【先别着急回收啊,等我调完 finalize 方法,再回收】
  3. FinalizerThread 线程会从 ReferenceQueue 中逐一取出每个 Finalizer 对象,把它们从链表断开并真正调用 finallize 方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3hJrA8Zr-1636598591232)(img/image-20210901122228916.png)]

  1. 由于整个 Finalizer 对象已经从 unfinalized 链表中断开,这样没谁能引用到它和狗对象,所以下次 gc 时就被回收了

缺点:

  • 无法保证资源释放:FinalizerThread 是守护线程,代码很有可能没来得及执行完,线程就结束了
  • 无法判断是否发生错误:执行 finalize 方法时,会吞掉任意异常(Throwable)
  • 内存释放不及时:重写了 finalize 方法的对象在第一次被 gc 时,并不能及时释放它占用的内存,因为要等着 FinalizerThread 调用完 finalize,把它从 unfinalized 队列移除后,第二次 gc 时才能真正释放内存
  • 有的文章提到【Finalizer 线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的CPU时间较少,因此它永远也赶不上主线程的步伐】这个显然是错误的,FinalizerThread 的优先级较普通线程更高,原因应该是 finalize 串行执行慢等原因综合导致。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值