Java四大引用:强、软、弱、虚引用

前言

Java 垃圾回收机制是面试核心考点,而四大引用类型 更是必问内容。JDK1.2 之后,Java 对对象引用进行了细分,分为强引用、软引用、弱引用、虚引用,四种引用决定了对象在 GC 时的回收时机、生命周期,各自有独特业务场景。

为什么有引用呢?

因为在Java世界里,对象的生死不仅由代码去决定,更由引用的类型掌控。通过四种引用可以做到:精细化控制对象生命周期,实现对象的提前回收、实现高效缓存、有效防止内存泄漏、感知对象回收事件

很多人只知道概念,但分不清使用场景、不会写测试代码,本文结合完整可运行 Demo,从定义、特性、代码案例、实际应用全方位拆解,看完轻松应对面试。

先统一测试实体类User,下面各个例子来使用

java 复制代码
public class User {
    public int id;
    public String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "[id=" + id + ", name=" + name + "] ";
    }
}

前置底层基础:可达性分析&引用队列

1. GC 判断对象存活:可达性分析算法

JVM 不再使用计数法,采用GC Roots 可达性分析 : 从 GC Roots(本地变量、静态变量、本地方法变量等)向下遍历引用链,链可达的对象全部存活,不可达对象判定为垃圾。 四大引用本质:改变「引用链存在时,GC 的回收策略」。

2. ReferenceQueue 引用队列(通用底层机制)

软 / 弱 / 虚引用都支持绑定ReferenceQueue,核心作用: 当引用包裹的对象被 GC 回收后,该引用对象本身会被 JVM 自动加入队列,业务代码可以轮询队列,感知对象销毁,做后置清理。

  • 软 / 弱引用:对象回收后,引用入队;
  • 虚引用:必须强制绑定队列,无队列则完全失去使用价值;
  • 强引用:不支持引用队列。

一、强引用:只要存在、永不回收

1.定义

日常代码中最普遍的引用方式:Object obj = new Object()。 只要强引用链可达,垃圾回收器永远不会回收该对象,哪怕 JVM 内存溢出 OOM 也不会回收。

2. 回收条件

必须切断全部强引用链,两种方式:

  1. 引用变量赋值null,断开引用;
  2. 引用变量离开自身作用域(方法执行完毕,局部变量销毁); 满足条件后,对象失去强引用,才会进入 GC 候选区。

3.完整示例

java 复制代码
import java.util.concurrent.TimeUnit;

public class StrongReferenceTest {
    public static void main(String[] args) {
        // 建立强引用
        User user = new User(1, "zhangsan");
        // 第二个强引用指向同一个对象
        User user1 = user;

        // 断开第一个强引用
        user = null;

        // 手动触发GC
        System.gc();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        // user1仍持有强引用,对象不会被回收,正常打印
        System.out.println(user1);
    }
}

输出结果

java 复制代码
[id=1, name=zhangsan] 

4.优缺点&场景

  • 优点:普通业务对象默认使用,简单直观;
  • 缺点:容易引发内存泄漏(集合、静态变量长期持有对象);
  • 使用场景:绝大多数普通业务对象、实体、工具类。

二、软引用:内存溢出前才回收

1.定义

SoftReference实现,内存充足时不回收,堆内存即将 OOM 前,GC 会回收所有仅被软引用关联的对象

如果回收完软引用对象后内存依旧不足,才抛出OutOfMemoryError

2.核心特性

  • 内存充裕:调用get()可以正常获取原始对象;
  • 内存紧张:GC 自动回收软引用对象,get()返回null
  • 适合做内存敏感缓存,内存不够自动释放缓存,避免 OOM。

3.测试案例

启动参数必须配置堆内存限制:-Xms10m -Xmx10m,固定堆大小制造内存紧张环境

java 复制代码
public class SoftReferenceTest {
    public static void main(String[] args) {
        // 构建软引用,注意:不能保留外层强引用,否则软引用失效
        SoftReference<User> userSoftRef = new SoftReference<>(new User(1, "zhangsan"));

        // 内存充足时,可以拿到对象
        System.out.println("GC前:" + userSoftRef.get());

        // 分配大字节数组,占用堆内存,制造内存压力
        try {
            // 申请7M字节数组,堆总大小仅10M,触发内存回收
            byte[] buffer = new byte[1024 * 1024 * 7];
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println("=====内存紧张后=====");
            // OOM之前,软引用对象会被回收,返回null
            System.out.println(userSoftRef.get());
        }
    }
}

运行现象: 内存充足时正常打印 User 对象;分配大数组后内存不足,GC 回收软引用,最终输出null

4. 实际应用场景

  1. 本地缓存框架:EHCache、Guava Cache,缓存数据使用软引用;
  2. 图片加载、大文件缓存:内存不足自动释放图片资源;
  3. Netty、IO 缓冲区内存缓存,平衡性能与内存占用。

5.优缺点

  • 优点:自动平衡缓存性能与内存,减少 OOM 概率;
  • 缺点:GC 时机不可控,无法精准控制缓存淘汰,不适合强一致性缓存。

三、软引用:只要GC,直接回收

1. 核心定义

WeakReference实现,生命周期极短:只要发生 GC,无论堆内存是否充足,仅被弱引用关联的对象一定会被回收

2. 核心特性

  1. 下一次 GC 执行,弱引用对象直接清空;
  2. get()方法在 GC 后返回null
  3. 不阻碍垃圾回收,适合临时存储非关键数据。

3. 完整测试代码

java 复制代码
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;

public class WeakReferenceTest {
    public static void main(String[] args) {
        // 创建弱引用对象,无外部强引用
        WeakReference<User> userWeakRef = new WeakReference<>(new User(1, "zhangsan"));
        System.out.println("GC之前:" + userWeakRef.get());

        // 手动触发GC
        System.gc();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("GC之后:" + userWeakRef.get());
    }
}

输出结果

java 复制代码
GC之前:[id=1, name=zhangsan] 
GC之后:null

4.适用场景

WeakHashMap,底层 Key 使用弱引用:

  • 缓存临时对象、会话信息;
  • 避免静态 Map 长期持有对象导致内存泄漏;
  • 当 Key 对象失去所有强引用,下次 GC 自动清除 Entry,释放内存。

四、虚引用:仅用于回收通知,无法获取对象

1. 核心定义

又称幽灵引用、幻影引用 ,通过PhantomReference实现,是最弱的引用。 两大核心特点:

  1. get()方法永远返回null,无法通过虚引用获取原始对象;
  2. 唯一作用:对象被 GC 回收时,将虚引用存入ReferenceQueue,开发者可监听队列,执行回收前清理工作。

2. 使用规范

虚引用必须绑定ReferenceQueue,无队列则无任何意义。

3.测试案例

java 复制代码
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;

public class PhantomReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        User obj = new User(1, "zhangsan");
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        // 创建虚引用,绑定引用队列
        PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);

        // 切断强引用,对象变为仅虚引用关联
        obj = null;

        boolean isCollected = false;
        while (!isCollected) {
            System.gc();
            TimeUnit.SECONDS.sleep(1);
            // 判断虚引用是否入队(代表对象已被回收)
            if (phantomRef.isEnqueued()) {
                isCollected = true;
            }
        }
        System.out.println("虚引用关联对象已被回收,虚引用进入队列");
    }
}

4.应用场景

  • 堆外内存回收监控(NIO DirectBuffer 底层使用虚引用释放堆外内存);
  • 对象销毁前资源清理:关闭文件流、释放网络连接、释放本地内存;
  • 内存监控、对象回收日志埋点,追踪对象销毁时机。

五、四大引用对比

引用类型 回收时机 获取对象 (get ()) 核心用途
强引用 断开所有引用后才回收 正常获取 普通业务对象,默认使用
软引用 内存即将 OOM 时回收 内存充足可获取,不足返回 null 内存敏感本地缓存
弱引用 只要 GC 就回收 GC 后返回 null WeakHashMap、临时缓存
虚引用 对象 GC 完毕,引用入队列 永远返回 null 回收监听、堆外内存释放

六、高频面试题拓展

1. 软引用和弱引用最大区别?

软引用会在内存不足时才回收,适合缓存;弱引用只要 GC 就回收,适合不影响内存的临时数据。

2. WeakHashMap 为什么不会内存泄漏?

Key 是弱引用,当 Key 对象外部无强引用,GC 会自动回收 Key,Map 自动清除对应 Entry,不会长期占用内存。

3. 虚引用不能获取对象,存在意义是什么?

无法操作对象,但可以通过ReferenceQueue感知对象被回收的时机,在对象彻底销毁前完成资源释放,比如 NIO 堆外内存释放。

4. 强引用如何避免内存泄漏?

  1. 集合使用完毕手动清空元素;
  2. 静态变量不要长期持有大对象;
  3. 局部对象使用后置为 null,缩短引用生命周期。

七、简洁记忆

  1. 强引用:日常 new,不置空永远不回收;
  2. 软引用:缓存专用,内存不够才回收;
  3. 弱引用:遇 GC 就回收,WeakHashMap 核心;
  4. 虚引用:拿不到对象,只做回收通知。