1. 引言

在Java编程中,我们通常使用硬引用(也称为强引用),而且大多数情况下甚至不会去考虑对象何时会被垃圾回收器清除。这是因为它们在大多数情况下是最好的选择。然而,有时候我们需要更精确地控制对象被清除的时间。本文将探讨硬引用与其他非硬引用类型之间的差异以及何时可以使用它们。

2. 强引用

强引用是默认的引用类型。大部分时候,我们可能不会去思考何时以及如何让垃圾回收器收集引用的对象。如果某个对象可以通过任何强引用可达,那么它就无法被垃圾回收。例如,当我们创建一个ArrayList对象并将其赋值给list变量:

List<String> list = new ArrayList<>;

由于list变量持有强引用,垃圾回收器无法收集这个列表。但如果我们将list变量设为null

list = null;

现在,只有当没有任何东西引用这个对象时,垃圾回收器才会进行收集。

3. 超出硬引用的范围

硬引用之所以是默认的,是有充分原因的。它们让垃圾回收器按预期工作,无需我们担心内存管理。尽管如此,在某些情况下,即使我们仍然持有对象的引用,我们也希望收集这些对象并释放内存。

4. 软引用

软引用告诉垃圾回收器,引用的对象可以在其自行决定的情况下被收集。对象可以在内存中保留一段时间,直到垃圾回收器认为有必要进行收集。这通常发生在JVM面临内存不足风险时。当仅通过软引用可达的所有软引用对象即将引发OutOfMemoryError异常时,它们都应该被清除。

我们可以轻松地使用软引用,只需将其包裹在我们的对象周围:

SoftReference<List<String>> listReference = new SoftReference<List<String>>(new ArrayList<String>());

如果需要获取所引用的对象,我们可以使用get方法。因为对象可能已被清除,所以我们需要检查是否存在:

List<String> list = listReference.get();
if (list == null) {
    // object was already cleared
}

4.1. 使用场景

软引用可用于使我们的代码对与内存不足相关的错误更加健壮。例如,我们可以创建一个对内存敏感的缓存,当内存紧张时自动淘汰对象。我们无需手动管理内存,因为垃圾回收器会为我们处理。

5. 弱引用

仅由弱引用引用的对象不会被阻止被收集。从垃圾回收的角度来看,它们实际上可能并不存在。如果一个被弱引用的对象需要保护不被清除,那么它也应该被硬引用引用。

5.1. 使用场景

弱引用最常用于创建规范化的映射。这些映射只包含可到达的对象。一个好的例子是WeakHashMap,它像正常的HashMap一样工作,但其键是弱引用的,当引用对象被清除时,键也会自动移除。

使用WeakHashMap,我们可以创建一个生命周期较短的缓存,自动清理不再被代码其他部分使用的对象。如果我们使用普通的HashMap,仅仅键存在于映射中就会阻止它被垃圾回收器清除。

6. 幽灵引用

类似于弱引用,幽灵引用也不会阻止垃圾回收器将对象放入待清除队列。区别在于幽灵引用必须在被清除之前手动从引用队列中获取,以便进行最终化操作。这意味着在对象被清除之前,我们可以决定要做什么。

6.1. 使用场景

幽灵引用非常适合实现一些细化的最终化逻辑,比finalize方法更可靠且灵活。让我们编写一个简单的方法,遍历引用队列,对所有引用进行清理:

private static void clearReferences(ReferenceQueue queue) {
    while (true) {
        Reference reference = queue.poll();
        if (reference == null) {
            break; // no references to clear
        }
        cleanup(reference);
    }
}

幽灵引用不允许我们使用get方法获取引用的对象。因此,扩展PhantomReference类以包含清理逻辑所需的重要信息是一种常见做法。

幽灵引用的其他重要用途包括调试和内存泄漏检测。即使不需要执行任何最终化操作,我们也可以使用幽灵引用来观察哪些对象何时被清除。

7. 总结

在这篇文章中,我们探讨了硬引用和其他非硬引用类型及其使用场景。了解到软引用可用于内存保护,弱引用用于规范化映射,而幽灵引用用于精细化的最终化。