说说你理解的JVM中四种引用类型?

 2023-01-22
原文作者:麒麟改bug 原文地址:https://juejin.cn/post/6977646223939502117

整体架构

202301011609532071.png

强引用

强引用是默认支持,当内存不足的时候,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会回收对象。

强引用是最常见的普通对象引用,只要还有强引用指向对象,对象就存活,垃圾回收器不会处理存活对象。一般把一个对象赋给一个引用变量,这个引用变量就是强引用。当一个对象被强引用变量所引用,它就处于可达状态,是不会被垃圾回收的,即使之后都不会再用到了,也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。

读者福利:Java并发编程面试真题共享!

对于一个普通对象,如果没有其他引用关系,只要超过了引用的作用域或者显式地将相应的强引用赋值为null,一般认为就是可以被垃圾回收了。(具体的回收时机看垃圾回收策略)

下例中,b就是强引用。

    public static void main(String[] args) {
            Object a = new Object();
            Object b = a;
            a = null;
            System.out.println(b);//java.lang.Object@4554617c
        }

软引用

软引用是一种相对强引用弱化了一些的引用,用

java.lang.ref.SoftReference实现,可以让对象豁免一些垃圾收集。当系统内存充足的时候,不会被回收;当系统内存不足的时候,会被回收。

软引用一般用于对内存敏感的程序中,比如高速缓存。

    import java.lang.ref.SoftReference;
    
    public class SoftReferenceDemo {
        public static void main(String[] args) {
            Object a = new Object();
            SoftReference<Object> softReference = new SoftReference<>(a);//软引用
            //a和软引用指向同一个对象
            System.out.println(a);//java.lang.Object@4554617c
            System.out.println(softReference.get());//java.lang.Object@4554617c
    
            //内存够用,软引用不会被回收
            a = null;
            System.gc();//内存够用不会自动gc,手动唤醒gc
            System.out.println(a);//null
            System.out.println(softReference.get());//java.lang.Object@4554617c
    
            //内存不够用时
            try{
                //配置Xms和Xmx为5MB
                byte[] bytes = new byte[1024*1024*30];//设置30MB超内存
            }catch (Throwable e){
                e.printStackTrace();
            }finally {
                System.out.println(a);//null
                System.out.println(softReference.get());//null
            }
        }
    }

使用场景

一个应用需要读取大量的本地图片,如果每次读取都从硬盘读取会严重影响性能,如果一次性全部加载到内存,内存可能会溢出。

可以使用软引用解决这个问题,使用一个HashMap来保存图片路径和图片对象管理的软引用之间的映射关系,内存不足时,JVM会自动回收缓存图片对象的占用空间,有效地避免了OOM(Out Of Memory)问题。

Map<String, SoftReference> imageCache = new HashMap<String, SoftReference>

弱引用

弱引用需要用

java.lang.ref.WeakReference实现,它比软引用的生存期更短,对于弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否够,都会回收该对象的占用内存。

    import java.lang.ref.WeakReference;
    
    public class SoftReferenceDemo {
        public static void main(String[] args) {
            Object a = new Object();
            WeakReference<Object> softReference = new WeakReference<>(a);//软引用
            //a和弱引用指向同一个对象
            System.out.println(a);//java.lang.Object@4554617c
            System.out.println(softReference.get());//java.lang.Object@4554617c
    
            //内存够用,弱引用也会被回收
            a = null;
            System.gc();//内存够用不会自动gc,手动唤醒gc
            System.out.println(a);//null
            System.out.println(softReference.get());//null
        }
    }

关于WeakHashMap

202301011609537712.png

    public static void weakHashMapTest() {
            Integer key = new Integer(1);
            String value = "李四";
            Map<Integer,String> weakHashMap = new WeakHashMap();
            weakHashMap.put(key, value);
            System.out.println(weakHashMap);//{1=李四}
            key = null;
            System.gc();
            System.out.println(weakHashMap);//{}
        }
    
        public static void hashMapTest() {
            HashMap<Integer,String> map = new HashMap<>();
            Integer key = 1;
            String value = "张三";
            map.put(key,value);
            System.out.println(map);//{1=张三}
            key = null;
            System.gc();
            System.out.println(map);//{1=张三}
        }

在HashMap中,键被置为null,唤醒gc后,不会垃圾回收键为null的键值对。但是在WeakHashMap中,键被置为null,唤醒gc后,键为null的键值对会被回收。

虚引用

虚引用要通过

java.lang.ref.PhantomReference类来实现,虚引用不会决定对象的生命周期,如果一个对象只有虚引用,就相当于没有引用,在任何时候都可能会被垃圾回收器回收。它不能单独使用也不能访问对象,虚引用必须和引用队列联合使用。

虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供一种确保对象被finalize以后,做某些事情的机制。

PhantomReference的get方法总是返回null,因此无法访问对应的引用对象,设置虚引用关联唯一的目的是在对象被收集器回收的时候收到一个系统通知,或者后续添加进一步的处理。Java允许使用finalize()方法在垃圾回收器将对象从内存中清理出去之前做一些必要的清理工作。【例如实现一个监控对象的通知机制】

引用队列

WeakReference和ReferenceQueue的联合使用效果:

    public static void weakReferenceTest() {
            Object a = new Object();
            ReferenceQueue<Object> queue = new ReferenceQueue<>();
            WeakReference<Object> weakReference = new WeakReference<>(a,queue);
            System.out.println(a);//java.lang.Object@4554617c
            System.out.println(weakReference.get());//java.lang.Object@4554617c
            System.out.println(queue.poll());//null
            System.out.println("-------------------");
            a = null;
            System.gc();
            System.out.println(a);//null
            System.out.println(weakReference.get());//null
            //虚引用在回收之前被加入到了引用队列中
            System.out.println(queue.poll());//java.lang.ref.WeakReference@74a14482
        }

PhantomReference和ReferenceQueue的联合使用效果:

    public static void phantomReferenceTest() {
            Object a = new Object();
            ReferenceQueue<Object> queue = new ReferenceQueue<>();
            PhantomReference<Object> phantomReference = new PhantomReference<>(a,queue);
            System.out.println(a);//java.lang.Object@4554617c
            System.out.println(phantomReference.get());//null
            System.out.println(queue.poll());//null
            System.out.println("-------------------");
            a = null;
            System.gc();
            System.out.println(a);//null
            System.out.println(phantomReference.get());//null
            //引用在回收之前被加入到了引用队列中
            System.out.println(queue.poll());//java.lang.ref.WeakReference@74a14482
        }

总结

202301011609542183.png

  • 强引用:不回收。
  • 软引用:内存不够就回收。
  • 弱引用:一定回收。
  • 虚引用:一定回收,get出来就是null,引用形同虚设,主要和引用队列联合使用,在finalize之前会被放到引用队列中。
  • 与根对象没有引用关系的:引用不可达,一定回收