• 基础 | java - [强引用、软引用、弱引用、虚引用]


    §1 Reference

    在 JDK 中,引用和对象一样,已经被抽象成了类,如下图
    在这里插入图片描述

    §2 各引用级别

    强引用 FinalReference

    • 强引用是最常见的引用,但 FinalReference 是非 public 的,因此不能如其他引用一样显示声明
    • 对象赋值给一个引用型变量,此变量就是一个强引用
    • 只要对象持有任意强引用,即可免疫 GC
      但可能造成无用对象依然被强引用指向,所以是内存泄漏的主因
      通常,对象超出引用作用于或显式置空(= null)就认为可以作为垃圾回收(还要参考垃圾回收策略)

    强引用 Finalizer

    • Finalizer 是对 FinalReference 的实现,也是非 public 的

    • 实现有 protected void finalize() throws Throwable { } 方法的对象会被识别为 Finalizer 对象

    • Finalizer 对象由 JVM 自动调用 register() 方法,完成创建,并在创建时将自己关联引用队列

    • Finalizer 通过 finalize() 定义了对象销毁时的自定义动作

    • Finalizer 对象至少需要两轮 GC 才能被回收,这是为了使被忘记释放资源的对象,即使真被忘了,也不会肯定不被回收

      • 第一轮:通过 finalize() 将 Finalizer 对象的连接放到它的引用队列中,但因为 Finalizer 本身就是个强引用所以还没有被 GC
      • 第二轮:若 Finalizer 对象只持有 Finalizer 引用队列的强引用了,才会通过正常方式 GC

    软引用 SoftReference

    • 软引用可以通过下面方式声明 Reference ra = new SoftReference<>(new AAA());
    • 支持有软引用的对象在 JVM 内存充足时免疫 GC,但不充足时会被回收,如下例
    • 内存不足 的界定
      • 快要 OOM 前,JVM 触发 GC
      • 执行后若依然要 OOM,认定内存不足,触发二次 GC,此时软引用纳入回收范围(下图中,可见两次 Full GC)
      • 若执行后依然要 OOM,那就 OOM 了
    • 可用于内存敏感型缓存 Map> cache = new HashMap<>();
    public class SoftReferenceDemo {
        public AAA b = new AAA();
        private Reference<AAA> ra = new SoftReference<>(new AAA());
    
        public Reference<AAA> getRa() {
            return ra;
        }
    
        public static void main(String[] args) {
            SoftReferenceDemo demo = new SoftReferenceDemo();
            System.out.println("================================");
            System.out.println(demo.b);
            System.out.println(demo.getRa().get());
            System.out.println("================================");
            try {
                int[] boom = new int[30*1024*1024];
            }catch (Throwable e){
                System.out.println("********************************");
                System.out.println("boom !!!");
                System.out.println("********************************");
            }finally {
                System.out.println("================================");
                System.out.println(demo.b);
                System.out.println(demo.getRa().get());
                System.out.println("================================");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述
    弱引用 WeakReference

    • 弱引用可以通过下面方式声明 Reference ra = new WeakReference<>(new AAA());
    • 只要 GC 就会被回收
    • 可以用来解决内存泄漏 Map map = new HashMap<>();WeakHashMap
      这种方式可以省略从容器中移除不需要的 key 的操作,以保证容器中的数据尽量齐全还不会导致内存泄漏
      但注意 线程不安全,同时只能用于存储 相对无关紧要 的数据,否则一个 GC 就丢了
    public class WeakReferenceDemo {
        public AAA b = new AAA();
        private Reference<AAA> ra = new WeakReference<>(new AAA());
    
        public Reference<AAA> getRa() {
            return ra;
        }
    
        public static void main(String[] args) {
            WeakReferenceDemo demo = new WeakReferenceDemo();
            System.out.println("================================");
            System.out.println(demo.b);
            System.out.println(demo.getRa().get());
            System.out.println("================================");
    
            System.gc();
    
            System.out.println("================================");
            System.out.println(demo.b);
            System.out.println(demo.getRa().get());
            System.out.println("================================");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    WeakHashMap
    WeakHashMap 是个 弱引用 版的 HashMap,其内部的 WeakHashMap.Entry 中 key 的类型为 WeakReference
    这意味着,一旦 GC,只要 key 不被其他强引用指向(比如是在字符串,在常量池里),WeakHashMap 就地清空
    见下面源码片段,和测试示例

    	// 此 Entry 非彼 Entry
    	Entry<K,V>[] table;
        public WeakHashMap(int initialCapacity, float loadFactor) {
            // 省略
            table = newTable(capacity);
            // 省略
        }
        // 此 Entry 中的 key 是 弱引用,所以一旦 GC,只要 key 不被其他强引用指向, WeakHashMap 就地清空
        private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
            V value;
            final int hash;
            Entry<K,V> next;
    		// 构造,注意 key 才是弱引用
    		Entry(Object key, V value,
                  ReferenceQueue<Object> queue,
                  int hash, Entry<K,V> next) {
                super(key, queue);
                this.value = value;
                this.hash  = hash;
                this.next  = next;
            }
            //省略
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    简单的测试
    在这里插入图片描述

    虚引用 PhantomReference

    • 虚引用,也称幽灵引用,是特殊引用
      • 不能往往对象(虚引用的 get() 永远返回 null)
      • 不影响对象的声明周期(不影响对象的 GC 抗性)
      • 仅持有虚引用的对象等同于 无引用,随时可能被 GC
    • 必须结合 ReferenceQueue 一起使用
    • 用于跟踪对象被 GC 的状态,实现当对象 finalize 后触发做某些事的机制,以实现比 finalization 机制更灵活的回收操作
      对象终止(finalization)机制:GC 对象前会先调用对象的 finalize() 方法,此方法允许重写。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等

    ReferenceQueue + WeakReference
    默认的,如果 SoftReference、WeakReference、PhantomReference 的对象 在声明时指定了 引用队列,如下例,则被 GC 时会执行 finalize() 方法,对象销毁后,上述引用本身不在指向对象,但 对象会存入 引用队列

    public void test() throws InterruptedException {
        ReferenceQueue<AAA> rf = new ReferenceQueue<>();
        Reference<AAA> weak = new WeakReference<>(new AAA(),rf);
    
        System.out.println(weak.get());//base.learning.AAA@3cd1a2f1
        System.out.println(rf.poll());//null
    
        System.gc();
        TimeUnit.SECONDS.sleep(1);
    
        System.out.println(weak.get());//null
        System.out.println(rf.poll());//引用还在 java.lang.ref.WeakReference@2f0e140b
    //        System.out.println(rf.poll().get());//引用不指向对象了 jnull
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ReferenceQueue + PhantomReference

    public void test() throws InterruptedException {
        ReferenceQueue<AAA> rf = new ReferenceQueue<>();
        Reference<AAA> phantom = new PhantomReference<>(new AAA(),rf);
    
        System.out.println(phantom.get());
        System.out.println(rf.poll());
    
        System.gc();
        TimeUnit.SECONDS.sleep(1);
    
        System.out.println(phantom.get());
        System.out.println(rf.poll());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    四大引用对比
    四大引用的区别,主要集中在 对 GC 抵抗能力

    对应类ReferenceSoftReferenceWeakReferencePhantomReference
    是否常见×××
    使用方式常规场景都是
    比如正常赋值
    但不能如其他引用直接声明
    Reference ra = new SoftReference<>(new AAA());类似 SoftReference配合引用队列
    new PhantomReference<>(new AAA(),rf);
    只持有此类引用的对象遇到垃圾回收时OOM 了都不收内存不足时回收只要 GC 就回收随时回收
    应用场景遍地都是内存敏感缓存(如 mybatis 中)解决容器不移除过期 key
    导致的内存泄漏
    WeakHashMap
    扩展 finalization 机制

    ReferenceQueuefinalize() 的区别

    • finalize() 是 JVM 回收对象之前自动调用,即在回收之前生效
    • ReferenceQueue 可以在对象被回收之后,额外保存其引用,即在回收之后生效

    本文部分内容摘自
    Java Reference详解
    JDK源码分析之FinalReference完全解读

  • 相关阅读:
    系统分屏后录音机向左右滑动报错问题
    Activiti 工作流引擎 详解
    【微信小程序AR】基于Kivicube零代码实现微信小程序AR
    数据结构——计数与归并非递归
    铝塑膜瑕疵检测系统
    JAVA毕业设计136—基于Java+Springboot+Vue的房屋租赁管理系统(源代码+数据库)
    iOS——SDWebImage源码解析
    关于YAML配置
    前端调式的一般方法
    为什么数组它的顺序读写会比较方便?
  • 原文地址:https://blog.csdn.net/ZEUS00456/article/details/126562660