0%

对象的引用

Java 对象的引用级别:从强到虚的内存管理艺术

在 Java 中,对象的引用并非只有 “存在” 或 “不存在” 两种状态,而是被细分为强引用、软引用、弱引用和虚引用四个级别。这种分级设计赋予了 JVM 更灵活的内存管理能力,允许开发者根据对象的重要性调整其生命周期,平衡内存使用与程序性能。本文将详细解析这四种引用类型的特性、使用场景及底层实现,帮助理解如何通过引用级别优化内存管理。

引用分级的核心目的

Java 引入多级别引用的核心诉求是:让对象的生命周期更灵活地响应内存状况。具体来说:

  • 对于核心对象(如用户会话、配置信息),需确保其始终驻留内存;
  • 对于次要对象(如缓存数据),可在内存紧张时主动回收,避免 OOM(内存溢出)。

通过引用分级,JVM 能在 “保留必要对象” 和 “释放冗余内存” 之间找到平衡,尤其适合内存敏感型应用(如缓存系统、大内存服务)。

强引用(Strong Reference)

强引用是最常见的引用类型,也是默认的引用方式。它直接关联对象,如同 “必需品”,JVM 绝不会主动回收强引用指向的对象。

特性与表现

  • 创建方式:通过new关键字实例化对象并赋值给变量,即形成强引用。

    1
    User user = new User(); // user 是指向 User 对象的强引用
  • 回收策略:只要强引用存在,无论内存是否紧张,JVM 都不会回收该对象。

  • 极端行为:若内存耗尽且无强引用可回收,JVM 会抛出 OutOfMemoryError,而非回收强引用对象。

  • 潜在问题:不当的强引用可能导致内存泄漏(如长期持有不再使用的对象引用)。

使用场景

强引用适用于必须始终存在的核心对象,如:

  • 程序运行的关键数据(如用户会话、全局配置);
  • 方法内的局部变量(随方法栈帧销毁自动释放)。

软引用(SoftReference)

软引用是 “可有可无” 的引用,其指向的对象在内存充足时保留,内存不足时(OOM 前)被回收,适合实现 “内存敏感的缓存”。

特性与表现

  • 创建方式:通过SoftReference类包装对象,需显式清除原强引用。

    1
    2
    3
    User user = new User();
    SoftReference<User> softRef = new SoftReference<>(user); // 软引用
    user = null; // 清除强引用,仅保留软引用
  • 回收时机:当 JVM 认为内存不足时(即将发生 OOM 前),会回收所有只被软引用指向的对象。

  • 获取对象:通过 softRef.get() 方法获取对象,若已被回收则返回 null

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SoftReferenceDemo {
public static void main(String[] args) {
// 创建对象并包装为软引用
User user = new User("张三");
SoftReference<User> softRef = new SoftReference<>(user);
user = null; // 清除强引用

// 首次获取(内存充足,对象存活)
System.out.println(softRef.get()); // 输出: User{name='张三'}

// 模拟内存不足(触发 GC 回收软引用对象)
List<byte[]> list = new ArrayList<>();
try {
while (true) {
list.add(new byte[1024 * 1024]); // 不断分配内存
}
} catch (OutOfMemoryError e) {
// 内存不足时,软引用对象已被回收
System.out.println(softRef.get()); // 输出: null
}
}
}

引用队列(ReferenceQueue)

软引用可与 ReferenceQueue 配合,当对象被回收时,软引用会被加入队列,便于跟踪回收状态:

1
2
3
4
5
6
7
8
ReferenceQueue<User> queue = new ReferenceQueue<>();
SoftReference<User> softRef = new SoftReference<>(user, queue);

// 检查队列中是否有被回收的软引用
Reference<? extends User> ref = queue.poll();
if (ref != null) {
System.out.println("软引用对象已被回收");
}

使用场景

软引用适合缓存不常用但重新创建成本高的对象,如:

  • 图片缓存(内存充足时保留,不足时释放);
  • 数据库查询结果缓存(避免频繁查询)。

弱引用(WeakReference)

弱引用比软引用 “更弱”,其指向的对象只能存活到下一次 GC 前,无论内存是否充足,GC 都会回收只被弱引用指向的对象。

特性与表现

  • 创建方式:通过WeakReference类包装对象,同样需清除原强引用。

    1
    2
    3
    User user = new User();
    WeakReference<User> weakRef = new WeakReference<>(user); // 弱引用
    user = null; // 清除强引用
  • 回收时机:只要发生 GC(无论内存是否充足),弱引用对象都会被回收。

  • 获取对象:通过 weakRef.get() 获取,GC 后返回 null

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
public class WeakReferenceDemo {
public static void main(String[] args) {
User user = new User("李四");
WeakReference<User> weakRef = new WeakReference<>(user);
user = null; // 清除强引用

System.out.println("GC 前: " + weakRef.get()); // 输出: User{name='李四'}

System.gc(); // 手动触发 GC
System.out.println("GC 后: " + weakRef.get()); // 输出: null
}
}

使用场景

弱引用适合生命周期短暂、可随时重建的对象,如:

  • 临时缓存(无需长期保留,GC 时自动清理);

  • 关联映射(如 WeakHashMap,键为弱引用,键对象回收后自动移除条目)。

    WeakHashMap 示例

    1
    2
    3
    4
    5
    6
    7
    WeakHashMap<User, String> map = new WeakHashMap<>();
    User key = new User("key");
    map.put(key, "value");
    key = null; // 清除键的强引用

    System.gc(); // GC 后,键被回收,条目自动移除
    System.out.println(map.size()); // 输出: 0

虚引用(PhantomReference)

虚引用是 “最弱” 的引用,完全不影响对象的生命周期,其唯一作用是在对象被回收时收到通知,无法通过虚引用获取对象实例。

特性与表现

  • 创建方式:通过PhantomReference类创建,必须与 ReferenceQueue 配合使用(无无参构造器)。

    1
    2
    3
    4
    User user = new User();
    ReferenceQueue<User> queue = new ReferenceQueue<>();
    PhantomReference<User> phantomRef = new PhantomReference<>(user, queue); // 虚引用
    user = null; // 清除强引用
  • 回收特性:虚引用对象随时可能被回收,且 phantomRef.get() 永远返回 null(无法通过虚引用访问对象)。

  • 通知机制:对象被回收时,虚引用会被加入关联的 ReferenceQueue,程序可通过队列感知回收事件。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
User user = new User("王五");
ReferenceQueue<User> queue = new ReferenceQueue<>();
PhantomReference<User> phantomRef = new PhantomReference<>(user, queue);
user = null; // 清除强引用

System.out.println(phantomRef.get()); // 输出: null(始终为 null)

System.gc(); // 触发 GC
// 从队列中获取被回收的虚引用
Reference<? extends User> ref = queue.remove(1000);
if (ref != null) {
System.out.println("虚引用对象已被回收"); // 输出此句
}
}
}

使用场景

虚引用主要用于跟踪对象的回收过程,如:

  • 资源清理(在对象回收时释放关联的 native 资源,如文件句柄、网络连接);
  • 监控 GC 行为(统计对象存活时间、回收频率)。

四种引用类型对比

引用类型 回收时机 能否通过引用获取对象 核心用途 关联类
强引用 永不主动回收(OOM 也不回收) 能(直接访问) 核心对象,必须保留 无(默认引用)
软引用 内存不足时(OOM 前) 能(get() 方法) 内存敏感缓存 SoftReference
弱引用 下次 GC 时(无论内存是否充足) 能(get() 方法) 临时缓存,自动清理 WeakReference
虚引用 随时可能回收 不能(get() 恒为 null) 跟踪对象回收,资源清理 PhantomReference

引用队列(ReferenceQueue)的统一作用

ReferenceQueue 是所有引用类型(软、弱、虚)的 “回收通知器”,当引用指向的对象被 GC 回收时,引用本身会被加入队列。其核心作用是:

  • 批量清理无效引用:避免内存中堆积大量已失效的引用对象;
  • 感知回收事件:在对象回收后执行后续操作(如资源释放)。

使用流程:

  1. 创建 ReferenceQueue 实例;
  2. 创建引用时关联队列(如 new SoftReference(obj, queue));
  3. 定期从队列中获取失效引用,进行处理(如 queue.poll()queue.remove())。

实践建议

  1. 避免滥用强引用:长期持有不需要的对象强引用会导致内存泄漏(如静态集合缓存过期数据)。
  2. 缓存场景优先软引用:软引用在内存充足时保留缓存,不足时自动释放,平衡性能与内存。
  3. 临时数据用弱引用:如 WeakHashMap 适合存储 “键无效后自动失效” 的映射(如缓存临时会话)。
  4. 资源清理用虚引用:虚引用是跟踪对象回收的唯一可靠方式,适合释放 native 资源

欢迎关注我的其它发布渠道