Java:强引用/软引用/弱引用/虚引用的区别

213

我已经阅读了关于Java中不同类型引用(强、软、弱、虚)的这篇文章,但我并不是很理解。

这些引用类型有什么区别,每种类型应该在什么情况下使用?


2
http://weblogs.java.net/blog/2006/05/04/understanding-weak-references - Louis Wasserman
4
我已经阅读了那份文件,但它没有帮助我理解其中的区别(可能是因为这是一份难以理解的文件)。 - user1204873
16
如果你阅读了那篇文章仍然不理解,你是否有具体的问题需要问呢?如果你只是说“请给我解释一下Foo”,“这是什么意思”,“我不懂”,而没有具体指出哪些部分让你感到困惑,那么回答会很困难。请提供具体问题。 - yshavit
1
@LouisWasserman,顶部链接已失效。 - Mehraj Malik
显示剩余2条评论
7个回答

173

Java提供了两种不同类型/类的引用对象: 强引用弱引用。弱引用又可以进一步分为软引用虚引用

  • 强引用
  • 弱引用
    • 软引用
    • 虚引用

我们来逐点讨论。

强引用对象

StringBuilder builder = new StringBuilder();

如果没有特别指定,builder 是强引用对象的默认类型/类。这种类型的引用会使所引用的对象不能被垃圾回收。也就是说,当一个对象被一系列强引用对象所引用时,它就不能被垃圾回收。

弱引用对象

WeakReference<StringBuilder> weakBuilder = new WeakReference<StringBuilder>(builder);

弱引用对象不是引用对象的默认类型或类,需要像上面的示例一样显式指定才能使用。这种引用使引用对象有资格进行GC。也就是说,如果内存中字符串构建器对象的唯一可访问引用是弱引用,则GC可以清除该字符串构建器对象。当内存中的对象仅由弱引用对象引用时,它将自动有资格进行GC。

弱度级别

可以列举两个不同的弱度级别:软引用幽灵引用

软引用对象基本上是略微强的弱引用对象:通常,它会抵抗GC周期,直到没有可用内存并且存在OutOfMemoryError风险(在这种情况下,它可以被删除)。

另一方面,幽灵引用对象只有在确切地知道对象已经从内存中有效删除时才有用:通常它们用于修复奇怪的finalize()复活行为,因为它们实际上不返回对象本身,只是帮助跟踪它们的内存存在

弱引用对象非常适合实现缓存模块。事实上,可以通过允许GC在强引用链不再引用对象/值时清除内存区域来实现某种自动清除。一个例子是保留弱键的WeakHashMap


2
在Q中添加:strongRef --> weakRef --> objA。现在,由于objA具有从strongRef间接引用,它是否会被垃圾回收? - samshers

83

弱引用(Weak Reference):

简单地说,弱引用是一种不足以强制对象保留在内存中的引用。使用弱引用可以利用垃圾回收器自身检测可达性的能力,从而无需手动进行检测。

软引用(Soft Reference):

软引用与弱引用几乎相同,只是它不太愿意立即丢弃所引用的对象。只有当一个对象只有弱引用时,它将在下一次垃圾回收周期中被丢弃,但若一个对象具有软引用,则通常会在一段时间内保留。

幽灵引用(Phantom Reference):

幽灵引用与软引用或弱引用截然不同。它对其对象的控制是如此微弱,以至于甚至无法检索到该对象,其 get() 方法总是返回 null。这种引用的唯一用途是跟踪何时将其加入到引用队列中,因为此时您知道其指向的对象已经死亡。

本文摘自:https://weblogs.java.net/blog/2006/05/04/understanding-weak-references


1
虽然这个回答中的所有内容看起来都是正确的,但在我看来,链接的网页上可能存在错误。Java.lang.ref包的Javadoc以及PhantomReference的Javadoc表明,在对象不再“幻象可达”之前,它不会被垃圾回收,这意味着(与SoftReference不同)必须在取消引用PhantomReference之前才能对其引用的对象进行垃圾回收...而它的入队并不表示相关内存已被释放。 - Theodore Murdock
2
说实话,我更愿意生活在那篇博客文章正确的世界里。 - Theodore Murdock
1
@TheodoreMurdock javadoc是正确的。幽灵引用根本不会阻碍垃圾回收。一旦对象被排队,即使通过finalizer也无法保存它,因为finalizer已经运行。它已经死了,但还没有消失。 - Leliel
1
@Leliel 实际上,幽灵引用在入队后确实会阻碍垃圾回收...我最近意识到这一点,因为一个错误导致清理线程提前退出。存在幽灵引用足以确保我的堆转储中保留了每个幽灵引用对象,无法进行回收...如果您未能处理队列,或者在处理队列时未使幽灵引用有资格进行gc(并且未清除()幽灵引用),则您的内存泄漏将包括幽灵引用和被引用的对象。 - Theodore Murdock
与软引用和弱引用不同,虚引用不会在加入队列后被垃圾回收器自动清除。通过虚引用可达的对象将一直保持这种状态,直到所有此类引用都被清除或它们本身变得不可达。 - theRiley
在Q中添加:strongRef --> weakRef --> objA。现在,由于objA具有从strongRef间接引用,它是否会被垃圾回收? - samshers

43

这篇文章可以帮助理解强引用、软引用、弱引用和虚引用。


简单概括一下:

如果你有一个强引用指向一个对象,那么这个对象永远不会被垃圾回收器(GC)回收。

如果你只有弱引用指向一个对象(没有强引用),那么这个对象将在下一个GC周期中被回收。

如果你只有软引用指向一个对象(没有强引用),那么这个对象只有在JVM内存耗尽时才会被GC回收。

我们创建虚引用指向一个对象,以跟踪对象何时进入ReferenceQueue。一旦知道了这一点,就可以执行精细的最终化操作。(这可以避免因虚引用不提供引用对象而意外地使对象恢复)。建议阅读这篇文章,了解更深入的细节。


因此,可以说强引用具有绝对的控制力(永远不会被GC回收)

软引用比弱引用更有优势(因为它们可以在JVM内存耗尽之前逃避GC周期)

弱引用比软引用更不强大(因为它们不能逃避任何GC周期,如果对象没有其他强引用,则会被回收)。


餐厅类比

  • 服务员 - GC
  • 您 - 堆中的对象
  • 餐厅空间 - 堆空间
  • 新客户 - 想在餐厅用餐的新对象

现在,如果你是强顾客(类比于强引用),那么即使有新客户进来或者发生什么事情,你也永远不会离开你的桌子(堆上的内存区域)。服务员无权要求您离开餐厅。

如果您是软顾客(类比于软引用),那么如果有新客户进来,服务员只有在没有其他空桌子可容纳新客户时才会让您离开桌子。(换句话说,如果新客户进来并且没有其他桌子留给这位新客户,服务员才会让您离开桌子)

如果您是弱顾客(类比于弱引用),那么服务员可以随时(任何时间)要求您离开餐厅 :P


6
该怎么翻译您想要的句子呢?"Damn, now I want to hear a story starting with 'A soft reference comes to a bar...'" - froh42
1
因为“服务员”这个故事而点赞...通过这个故事很容易理解... - Manan Shah
我对《餐厅类比故事》印象深刻,这个例子真的很容易理解,所以我点赞了。 - chanduthedev
我对《餐厅类比故事》印象深刻,这个例子真的很容易理解,所以我给它点了赞。 - undefined

27
SoftReferenceWeakReference 的简单区别由Android开发者文档提供。一个 SoftReference 和一个 WeakReference 的区别在于决定清除和排队引用的时间点:
  • SoftReference 应该尽可能晚地被清除和排队,即在VM面临内存耗尽危险时。

  • WeakReference 可以在已知弱引用时立即被清除和排队。


17

您提到的三个术语主要与对象是否有资格进行垃圾回收相关。

弱引用 :: 它是一种不足以强制对象保留在内存中的引用。垃圾回收器会决定是否将该对象收集为垃圾。 您无法阻止GC对其进行回收

软引用 :: 它与弱引用差不多。但可以说它比弱引用更强地持有对象以避免被垃圾回收。

如果垃圾收集器在第一次生命周期中收集了弱引用,那么它将在下一次垃圾回收周期中收集软引用。

强引用 :: 它与上述两种引用相反。 它们不太可能被垃圾回收(大多数情况下永远不会被回收)。

您可以参考以下链接获取更多信息:

http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/ref/Reference.html


4
我认为这里有错误 - "如果垃圾收集器在第一个生命周期中收集了弱引用,它将在下一次垃圾收集周期中收集软引用。" 这并不一定是正确的,你怎么能确定它们会在连续的GC运行中发生呢?GC可以允许软引用对象甚至在第二次和第三次运行中继续存在。没有文件记录这一点,如果有的话,请提供链接。 - Saurabh Patil
2
另外,你的回答有点模糊不清。看看这句话:“它与弱引用几乎相同。但是可以说它比垃圾收集器中的弱引用对对象保持得更加牢固。”- 他明显是在询问两者的差异而非相似之处。所有这些词语只会让这个话题更加混乱而非清晰明了。 - Saurabh Patil
@SaurabhPatil -- 您的评论被错过了。以下是答案。1. “他显然在问区别而不是相似之处”--请参考问题描述(不仅仅是标题)“请给我一些建议,并请给我一些描述的例子”。2. “但是您可以说它会更好地保持对象....”我认为SOF提供了一个选项,可以对答案进行投票和新回答。 - Sabya

13

强引用

这些是我们日常编码使用的常规对象引用:

Employee emp = new Employee();
变量“emp”持有一个Employee 对象的强引用,通过任何一条强引用链可访问到的对象都不符合垃圾回收条件。通常情况下这是我们期望的,但并非总是如此。现在假设我们需要从集合或映射中获取大量从数据库中检索的员工,并且需要定期对它们进行大量处理,为了保持性能,我们将把它们放在缓存中。 这样做是好的,但现在我们需要不同的数据,并且我们不需要那些Employee对象,除了缓存以外没有地方引用它们。由于这些对象没有使用但仍未满足垃圾回收条件,会导致内存泄漏,但我们无法将这些对象从缓存中删除,因为没有对它们的引用。 因此,我们要么手动清空整个缓存(比较繁琐),要么使用其他类型的引用,例如Weak References。
弱引用不会将对象固定在内存中,如果没有来自其他引用的引用,它将在下一个GC周期中被垃圾回收。我们可以使用Java提供的WeakReference类创建上述类型的缓存,这将不会存储没有被其他地方引用的对象。
WeakReference<Cache> cache = new WeakReference<Cache>(data);
要访问数据,您需要调用cache.get()。如果弱引用被垃圾回收,则此调用可能返回null:必须检查返回的值以避免NPE。
Java提供了使用弱引用的集合,例如WeakHashMap类将键(而不是值)存储为弱引用。如果键被GC'd,则值也将自动从映射中删除。
由于弱引用也是对象,因此我们需要一种清理它们的方法(当它们正在引用的对象已被GC'd时,它们不再有用)。如果在弱引用的构造函数中传递ReferenceQueue,则垃圾回收器将在它们被最终处理或GC之前将该弱引用附加到ReferenceQueue中。您可以定期处理此队列并处理死引用。
软引用
SoftReference类似于WeakReference,但不太可能被垃圾回收。软引用根据内存需求被垃圾回收器自行清除。虚拟机保证在抛出OutOfMemoryError之前,所有对轻松可达对象的软引用都将被清除。
幽灵引用
幽灵引用是所有引用类型中最弱的,对它们调用get将始终返回null。对象在完成后但分配的内存尚未回收之前被幽灵引用。与弱引用在它们被最终处理或GC之前入队不同,幽灵引用很少使用。
那么它们有什么用?当构造幽灵引用时,您必须始终传递ReferenceQueue。这表示您可以使用幽灵引用来查看对象何时被GC'd。
嘿,如果弱引用在其被视为finalize但尚未GC'd时入队,我们可以在finalizer块中创建对该对象的新强引用并防止对象被GC'd。是的,您可以,但最好不要这样做。为了检查此情况,每个对象的GC周期将至少发生两次,除非该对象仅由幽灵引用引用。这就是为什么即使您的内存包含大量垃圾,也可能会耗尽堆空间。幽灵引用可以防止这种情况发生。
您可以在我的文章Java中的引用类型(强、软、弱、幽灵)中阅读更多信息。

你写道,如果弱引用没有被其他引用所引用,那么它们将在下一个循环中被垃圾回收...但是强引用不也应该发生同样的事情吗?如果强引用没有被任何方式访问,则会被清除...那么区别在哪里呢...?#困惑 - filemonczyk
1
如果一个对象被s1(强引用)和s2(强引用)引用,那么只有当s1和s2都取消引用时,该对象才不会被垃圾回收。但是,如果对象被s1(弱引用)和s2(强引用)引用,则当它仅从s2中取消引用时,对象将在下一个GC周期中成为可回收的,因为s1是一个弱引用,如果对象除了弱引用之外没有其他引用,它就可以被回收。 - Naresh Joshi

11

4个引用级别 - 强引用、弱引用、软引用、虚引用

强引用 - 一种使被引用对象不符合垃圾回收的引用,例如StringBuilder。

弱引用 - 可以被垃圾回收的引用。

软引用 - 一种在内存可用时对象可以被垃圾回收的引用。最适合图像缓存。它们将保持对象直到内存可用。

虚引用 - 一种直接可被垃圾回收的引用。仅用于了解何时从内存中删除对象。

应用:

  1. 允许您确定对象何时完全从内存中删除。

  2. finalize()方法被重载时,GC可能无法按时回收两个类的GC适用对象。因此,虚引用使它们在finalize()之前就变得可回收,这就是为什么即使大部分堆都是垃圾时也会出现OutOfMemoryErrors

弱引用非常适合实现缓存模块。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接