Java Reference 详解 (强引用、软引用、弱引用、虚引用)

 
Java Reference 详解 (强引用、软引用、弱引用、虚引用)

 

 

1、简介

      像数据字典、用户信息等一些常用的数据,我们经常会放在应用缓存中。但假若放到缓存里的数据量非常大,那就会出现一个问题:由于数据量过大可能会导致内存不足,而不单单是提升性能了。假如说把表中所有数据都放入缓存,那么缓存的可能会占据大部分jvm的内存或者索性直接产生一个OOM错误。最佳的方案是如果我们可以创造一种可以按需扩展和收缩的动态缓存,当我们的数据量需要而内存充裕的时候可以适当增加,但内存不足是可以按不同方案对其进行回收。我们的应用在运行过程中会产生很多对象,这些对象驻留在内存中,它们大小不同,重要性不同,使用频率不同,生命周期不同,比如有些对象只要应用启动就一直存活直到应用停止,而有些对象生命周期与创建它的线程相同,还有些对象只作临时变量短时间就消亡。所以很容易想象对于不同的对象,我们希望对他们的创建销毁采取不同的策略。于是设计者们在java 1.2加入了reference,引入了强引用、软引用、弱引用、虚引用这四个概念。 使jvm可以对不同的reference对象采取不同的回收策略以达到提高应用性能的目的。

java.lang.ref 包中就有以下几种不同的reference类型。继承关系如下:

      

 

2、Reference 介绍 

reference构造函数

Reference(T referent) {
 this(referent, null);
}
  
Reference(T referent, ReferenceQueue<? super T> queue) {
 this.referent = referent;
 this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

       其内部提供2个构造函数,一个带queue,一个不带queue。其中queue的意义在于,我们可以在外部对这个queue进行监控.即如果有对象即将被回收,那么相应的reference对象就会被放到这个queue里。我们拿到reference,就可以再作一些事务。而如果不带的话,就只有不断地轮训reference对象,通过判断里面的get是否返回null(phantomReference对象不能这样作,其get始终返回null,因此它只有带queue的构造函数).这两种方法均有相应的使用场景,取决于实际的应用。如weakHashMap中就选择去查询queue的数据,来判定是否有对象将被回收。而ThreadLocalMap,则采用判断get()是否为null来作处理。Reference的直接子类都是由jvm定制化处理的, 因此在代码中直接继承于Reference类型没有任何作用,只能继承于它的子类, 相应的子类类型包括以下几种:

 

3、引用强度与 GC 行为

Java 的四种引用类型本质上代表了不同的引用强度,这直接决定了垃圾回收器(Garbage Collector, GC) 在回收对象时的行为。

它们的引用强度从强到弱依次为:
强引用(Strong Reference) > 软引用(Soft Reference) > 弱引用(Weak Reference) > 虚引用(Phantom Reference)

1. 强引用(Strong Reference)

这是我们日常编程中使用的默认引用类型。

  • 定义:类似于 Object obj = new Object(); 这样的引用。只要强引用关系还存在,垃圾回收器就永远不会回收掉被引用的对象。

  • 生命周期:只有当这个强引用被显式地设置为 nullobj = null;),或者其作用域结束(例如方法内的局部变量),GC 才会在下次回收周期中考虑回收这个对象。

  • 内存不足(OOM):如果一个对象存在强引用,但 JVM 内存不足,无法回收它,JVM 会抛出 OutOfMemoryError 而不是去回收这个对象。

代码示例:

public class StrongReferenceExample {
    public static void main(String[] args) {
        Object strongRef = new Object(); // 创建一个强引用
        // ... 使用 strongRef
        strongRef = null; // 此时,对象不再有强引用,可以被GC回收
        System.gc(); // 只是建议JVM进行GC,并不保证立即执行
    }
}

 

 

2. 软引用(Soft Reference)

  • 定义:用于描述一些还有用但非必需的对象。

  • GC 行为:

1、在系统内存充足时,GC 不会回收软引用关联的对象。

2、当系统内存不足,即将发生 OutOfMemoryError 之前,GC 会回收这些仅被软引用关联的对象。

  • 典型用途:实现内存敏感的缓存。例如,缓存大量图片数据。当内存紧张时,缓存会被自动释放,避免OOM;当内存充足时,缓存能提供高性能。

  • 如何使用:通过 java.lang.ref.SoftReference 类实现。

代码示例:

import java.lang.ref.SoftReference;

public class SoftReferenceExample {
    public static void main(String[] args) {
        // 创建一个强引用对象
        Object strongRef = new Object();
        // 用一个强引用对象创建一个软引用
        SoftReference<Object> softRef = new SoftReference<>(strongRef);

        // 断开强引用,现在对象只剩下一个软引用
        strongRef = null;

        // 在内存充足的情况下,GC不会回收它
        System.gc();
        System.out.println(softRef.get()); // 很可能还能获取到对象

        // 模拟内存紧张的情况(需要大量分配内存)
        try {
            List<byte[]> list = new ArrayList<>();
            while (true) {
                list.add(new byte[1024 * 1024]); // 不断分配1MB的数组
            }
        } catch (OutOfMemoryError e) {
            // 当发生OOM后,软引用指向的对象很可能已经被回收了
            System.out.println("内存不足,软引用已被回收: " + softRef.get()); // 输出 null
        }
    }
}

 

3. 弱引用(Weak Reference)

  • 定义:强度比软引用更弱。

  • GC 行为:无论当前内存是否充足,只要垃圾回收器一运行,就会回收掉只被弱引用关联的对象。

  • 典型用途:

    1. 规范化映射(Canonicalizing Mapping):最经典的例子是 WeakHashMap。它的 Key 是弱引用的。当 Key 对象除了在 WeakHashMap 中被引用外,没有其他强引用时,下次 GC 这个 Key-Value 对就会被自动移除。非常适合做缓存,但缓存失效是“尽己所能”的。

    2. 在某些场景下防止内存泄漏,例如监听器列表。

  • 如何使用:通过 java.lang.ref.WeakReference 类实现。

代码示例:

import java.lang.ref.WeakReference;

public class WeakReferenceExample {
    public static void main(String[] args) {
        Object strongRef = new Object();
        WeakReference<Object> weakRef = new WeakReference<>(strongRef);

        System.out.println("GC前: " + weakRef.get()); // 有值

        // 断开强引用
        strongRef = null;

        // 执行GC(弱引用无法在GC中存活)
        System.gc();

        // 给GC一点时间
        try { Thread.sleep(100); } catch (InterruptedException e) {}

        System.out.println("GC后: " + weakRef.get()); // 输出 null
    }
}

 

4. 虚引用(Phantom Reference)

  • 定义:也称为“幻象引用”或“幽灵引用”,是最弱的一种引用关系。

  • GC 行为:

    • 一个对象是否有虚引用,完全不会影响其生命周期。你甚至无法通过虚引用来获取一个对象(它的 get() 方法总是返回 null)。

    • 它唯一的目的是:在对象被垃圾回收器回收时,能够收到一个系统通知。

  • 典型用途:用于在对象被 GC 之后,执行一些特定的清理操作,例如确保 native 资源(堆外内存,如 DirectByteBuffer 使用的内存)被释放。它比 finalize() 方法更可靠、更灵活。

  • 工作机制:必须与 ReferenceQueue(引用队列) 联合使用。

    • 当 GC 准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之后,把这个虚引用加入到与之关联的 ReferenceQueue 中。

    • 程序可以通过检查 ReferenceQueue 中是否有虚引用,来判断对象是否已经被回收,然后执行后续的清理工作。

  • 如何使用:通过 java.lang.ref.PhantomReference 类实现。

代码示例:

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceExample {
    public static void main(String[] args) {
        Object strongRef = new Object();
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        PhantomReference<Object> phantomRef = new PhantomReference<>(strongRef, queue);

        System.out.println("PhantomReference.get(): " + phantomRef.get()); // 总是 null

        // 断开强引用
        strongRef = null;

        // 执行GC
        System.gc();
        try { Thread.sleep(100); } catch (InterruptedException e) {}

        // 检查引用队列,看是否有引用被加入
        Reference<?> refFromQueue = queue.poll();
        if (refFromQueue != null) {
            System.out.println("对象已被回收,虚引用被加入到队列中。");
            // 在这里可以执行相关的清理操作
        }
    }
}

 

总结:

image

 通过合理利用这四种引用类型,你可以更精细地控制对象的生命周期,从而提升应用程序的内存管理效率和健壮性。

 

posted @ 2023-06-17 12:30  邓维-java  阅读(760)  评论(0)    收藏  举报