欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java的强引用,软引用,弱引用,WeakHashMap,ReferenceQueue和虚引用详解

程序员文章站 2024-02-11 23:28:16
...

java引用继承结构图:
Java的强引用,软引用,弱引用,WeakHashMap,ReferenceQueue和虚引用详解

强引用

特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。
当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。

/**
 * 强引用测试
 */
public class StrongRefTest {
    public static void main(String[] args) {
        Object o = new Object();
        Object o2 = o;
        // 置空
        o = null;
        // 调用垃圾回收
        System.gc();
        System.out.println(o);
        System.out.println(o2);
    }
}

o2不会被垃圾回收,o2是强引用,指向了一个对象,就算堆内存不足也不会被回收。

软引用(SoftReference)

软引用是一种相对强引用的弱化,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。

对于只有软引用的对象来说:

当系统内存充足时它不会被回收
当系统内存不足时它会被回收

软引用通常在内存敏感的程序中,比如高速缓存就用用到软引用,内存够用的时候就保留,不够用的时候就回收。

如下代码:

/**
 * 软引用测试
 */
public class SoftRefTest {

    public static void main(String[] args) {
        Object o1 = new Object();
        // 把o1引用赋值给软引用对象
        SoftReference<Object> softReference = new SoftReference<>(o1);

        // 让01 = null,使只有软引用指向Object
         o1 = null;

        System.out.println(o1);
        System.out.println(softReference.get());
        System.out.println("=====设置大对象后===========");

        // (先设置堆内存大小 -Xms10m -Xmx10m)制造一个大对象,让堆内存溢出
        try {
            byte[] bytes = new byte[10 * 1024 *1024];
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(o1);
            System.out.println(softReference.get());
        }
    }
}

运行结果:

null
java.lang.Object@4554617c
=====设置大对象后===========
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.fangyajun.javastudy.reference.SoftRefTest.main(SoftRefTest.java:24)
null
null

从结果看出,在堆内存够用之前,软引用不会被回收,有值aaa@qq.com
在堆内存不够用后,软引用会被回收,结果为null.

软引用在实际中有重要的应用,例如浏览器的后退按钮。
按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

(1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
(2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出
这时候就可以使用软引用

假如有一个应用场景需要读取大量的本地图片:

如果每次读取图片都从硬盘读取则会严重影响性能。
如果一次性加载到内存中可能造成内存溢出

此时使用软引用可以解决这个问题。
设计思路:用一个HashMap来保存图片中的路径和相对应图片对象关联的软引用之间的映射关系,在内存不足的时候JVM会自动回收这些缓存图片所占用的空间,从而有效的避免OOM问题
Java的强引用,软引用,弱引用,WeakHashMap,ReferenceQueue和虚引用详解

弱引用(WeakReference)和 WeakHashMap

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

代码示例:

public class WeakRefTest {
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> weakReference = new WeakReference<>(o1);
        o1 = null;

        System.out.println(o1);
        System.out.println(weakReference.get());
        System.gc();
        System.out.println("GC后===========");
        System.out.println(o1);
        System.out.println(weakReference.get());
    }
}

运行结果:

null
java.lang.Object@4554617c
GC后===========
null
null

从结果可以看出垃圾回收后,弱引用被回收

弱引用使用场景:
1. 如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 WeakReference 来记住此对象。
2. 当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

WeakHashMap

  1. WeakHashMap 继承于AbstractMap,实现了Map接口。和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
  2. 不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。

代码示例:

public class WeakHashMapTest {
    public static void main(String[] args) {
        Integer key = new Integer(1000);
        String value = "HashMap";

        HashMap<Integer, String> hashMap = new HashMap<>();
        hashMap.put(key, value);

        // 让key = null ,使只有hashMap中的key指向"key"
        key = null;
        System.out.println("===========使用HashMap测试===============");
        System.out.println(key);
        System.out.println(hashMap);

        System.out.println("------gc后--------");
        System.gc();
        System.out.println(key);
        System.out.println(hashMap);

        System.out.println("===========使用WeakHashMap测试===============");
        weakHashMapTest();

    }

    public static void weakHashMapTest() {
        Integer key = new Integer(1000);
        String value = "HashMap";

        WeakHashMap<Integer, String> hashMap = new WeakHashMap<>();
        hashMap.put(key, value);

        // 让key = null ,使只有hashMap中的key指向"key"
        key = null;
        System.out.println(key);
        System.out.println(hashMap);

        System.out.println("------gc后--------");
        System.gc();
        System.out.println(key);
        System.out.println(hashMap);
    }
}

运行结果:

===========使用HashMap测试===============
null
{1000=HashMap}
------gc后--------
null
{1000=HashMap}
===========使用WeakHashMap测试===============
null
{1000=HashMap}
------gc后--------
null
{}

从结果可以看出:

  1. 使用HashMap时,当HashMap中的key只有一个引用指向的时候,因为HashMap中的key是强引用,所以gc后HashMap的元素不会被回收。

  2. 但是使用WeakHashMap时,当WeakHashMap中的key只有自己使用的时候(也就是key只有自己指向key对象),当gc后,WeakHashMap中的该元素就会被垃圾回收器回收。

虚引用(PhantomReference)

虚引用需要java.lang.ref.PhantomReference类来实现

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中

虚引用主要作用

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

代码示例:
虚引用只有一个构造方法,必出传入引用队列

public class PhantomReferenceTest {
    public static void main(String[] args) {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        // 虚引用只有一个构造方法,必出传入引用队列
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);

        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println("引用队列:" + referenceQueue.poll());

        // 使 o1 = null;可以 被垃圾回收
        o1 = null;
        System.out.println("==========gc后=============");
        System.gc();
        System.out.println(o1);
        System.out.println(phantomReference.get());
        System.out.println("引用队列:" + referenceQueue.poll());
    }
}

运行结果:

java.lang.Object@4554617c
null
引用队列:null
==========gc后=============
null
null
引用队列:java.lang.ref.PhantomReference@74a14482

从结果可以看出,虚引用在垃圾回收之前就相当与没有引用一样,当被垃圾回收后,虚引用会被加入到引用队列中。以便在对象销毁后,我们可以做一些自己的事情

ReferenceQueue(引用队列)

引用队列的主要作用是,当一个引用被垃圾回收之前,会被放入到引用队列中。

对于软引用和弱引用,我们希望当一个对象被gc掉的时候通知用户线程,进行额外的处理时,就需要使用引用队列了。ReferenceQueue即这样的一个对象,当一个obj被gc掉之后,其相应的包装类,即ref对象会被放入queue中。我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。

代码示例:

public class ReferenceQueueTest {
    public static void main(String[] args) {
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();

        Object o1 = new Object();
        // 构造方法可以传入一个引用队列
        WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
        o1 = null;
        
        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println("引用队列:" + referenceQueue.poll());

        System.out.println("=====gc后=========");
        System.gc();
        System.out.println(o1);
        System.out.println(weakReference.get());
        System.out.println("引用队列:" + referenceQueue.poll());
    }
}

运行结果:

null
java.lang.Object@4554617c
引用队列:null
=====gc后=========
null
null
引用队列:java.lang.ref.WeakReference@74a14482

从结果可以看出,在gc后,弱引用会被放入到引用队列中。

特别注意:在实际程序设计中一般很少使用弱引用与虚引用,使用软用的情况较多,这是因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。

相关标签: JVM