Java 中的 SoftReference 和 WeakReference 有什么区别?

答案

从 Ethan Nicholas 的《 理解弱引用》中

参考不足

简而言之, 弱引用是一种强度不足以迫使对象保留在内存中的引用。弱引用使您可以利用垃圾收集器的能力来确定您的可达性,因此您不必自己做。您创建像这样的弱引用:

WeakReference weakWidget = new WeakReference(widget);

然后在代码中的其他位置,您可以使用weakWidget.get()来获取实际的Widget对象。当然,弱引用的强度不足以阻止垃圾回收,因此您可能会发现(如果没有对窗口小部件的强引用) weakWidget.get()突然开始返回null

...

软参考

软引用与弱引用完全一样,不同之处在于软引用不急于丢弃它所引用的对象。只有弱可达性的对象(对其最强的引用是WeakReferences )将在下一个垃圾回收周期中被丢弃,但是柔弱可达性的对象通常会停留一段时间。

不需要 SoftReferences的行为与WeakReferences不同,但是实际上,只要有足够的内存,通常会保留可软访问的对象。这使它们成为缓存(例如上述图像缓存)的良好基础,因为您可以让垃圾回收器担心对象的可访问性(一个高度可访问的对象将永远不会从缓存中删除)。需要他们正在消耗的内存。

彼得 · 凯斯勒(Peter Kessler)在评论中添加:

Sun JRE 确实将 SoftReference 与 WeakReferences 区别对待。如果可用内存没有压力,我们尝试保留由 SoftReference 引用的对象。一个细节:“-client” 和 “-server” JRE 的策略不同:-client JRE 倾向于通过清除 SoftReferences 而不是扩展堆来减小占用的空间,而 - server JRE 尝试保持您的占用空间。通过扩大堆(如果可能)而不是清除 SoftReferences,可以提高性能。一种尺寸并不适合所有尺寸。

急切地收集弱引用。如果 GC 发现某个对象是弱可访问的(仅可通过弱引用来访问),它将立即清除对该对象的弱引用。这样,它们对于保留对对象的引用很有用,您的程序也为该对象保留(强烈引用)“关联信息”,例如有关类的缓存反射信息或对象的包装等。在与对象关联的对象进行 GC 编辑后,没有任何保留意义。当弱引用被清除后,它将进入一个队列中,该队列将由您的代码在某个地方轮询,并且它也会丢弃关联的对象。也就是说,您保留了有关对象的额外信息,但是一旦该对象所指的对象消失了,就不需要该信息。实际上,在某些情况下,您甚至可以继承 WeakReference 的子类,并将有关对象的相关额外信息保留在 WeakReference 子类的字段中。 WeakReference 的另一个典型用法是与 Maps 结合使用,以保留规范实例。

另一方面,SoftReferences 非常适合缓存外部可重新创建的资源,因为 GC 通常会延迟清除它们。尽管可以保证所有 SoftReferences 在抛出 OutOfMemoryError 之前都会被清除,所以从理论上讲它们不会导致 OOME [*]。

典型的用例示例是保留文件中内容的解析形式。您将实现一个系统,在该系统中加载文件,解析文件并将 SoftReference 保留到解析表示形式的根对象。下次需要该文件时,您将尝试通过 SoftReference 检索它。如果可以检索它,则可以省去另一次加载 / 分析,如果 GC 同时清除了它,则可以重新加载。这样,您就可以利用空闲内存进行性能优化,但是不必冒险使用 OOME。

现在为 [*]。保持 SoftReference 本身不会导致 OOME。另一方面,如果您错误地将 SoftReference 用作要使用 WeakReference 的任务(即,您以某种方式强烈引用了与 Object 关联的信息,并在清除 Reference 对象时将其丢弃),则可以在 OOME 中运行轮询 ReferenceQueue 并丢弃关联对象的代码可能恰巧未及时运行。

因此,决定取决于使用情况 - 如果要缓存构造成本高昂但仍可从其他数据重构的信息,请使用软引用 - 如果要保留对某些数据的规范实例的引用,或者要在没有 “拥有” 该对象的情况下拥有对对象的引用(从而防止其被 GC 引用),请使用弱引用。

在 Java 中 ; 从最强到最弱的顺序为:强,弱,弱和幻影

强引用是一种普通引用,可以保护所引用的对象免遭 GC 收集。即永不垃圾收集。

Soft 引用可以由垃圾收集器进行收集,但是直到需要其内存时才可能被收集。即垃圾收集OutOfMemoryError之前。

弱引用是一种引用,它不能保护引用的对象免受 GC 的收集。也就是说,当没有强引用或软引用时,垃圾将收集。

幻像引用是对对象的引用,它在完成终结之后但在收回其分配的内存之前被幻像引用

资源

打个比方:假设 JVM 是一个王国,对象是该王国的国王,而 GC 是该王国的攻击者,试图杀死国王(对象)。

  • 当 King 是Strong 时 ,GC 无法杀死他。
  • 当 King 是Soft 时 ,GC 会攻击他,但是 King 会保护王国直到资源可用。
  • 当金是弱者时 ,GC 攻击了他,但却在没有保护的情况下统治了王国。
  • 当国王是幻影时 ,GC 已经杀死了他,但是国王可以通过他的灵魂来获得。

弱参考 http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ref/WeakReference.html

原理: weak reference与垃圾回收有关。通常,具有一个或多个reference对象将不符合垃圾回收的条件。
当它是weak reference时,以上原理不适用。如果一个对象仅具有与其他对象的弱引用,则可以对其进行垃圾回收。

让我们看下面的示例:我们有一个带对象的Map ,其中 Key 引用了一个对象。

import java.util.HashMap;   
public class Test {

    public static void main(String args[]) {
        HashMap<Employee, EmployeeVal> aMap = new 
                       HashMap<Employee, EmployeeVal>();

        Employee emp = new Employee("Vinoth");
        EmployeeVal val = new EmployeeVal("Programmer");

        aMap.put(emp, val);

        emp = null;

        System.gc();
        System.out.println("Size of Map" + aMap.size());

    }
}

现在,在程序执行期间,我们使emp = null 。保留键的Map在这里没有意义,因为它为null 。在上述情况下,对象不会被垃圾回收。

WeakHashMap

WeakHashMap是一种当无法再从Map检索条目时( key-to-value mappings )的条目。

让我用WeakHashMap展示上面的例子

import java.util.WeakHashMap;

public class Test {

    public static void main(String args[]) {
        WeakHashMap<Employee, EmployeeVal> aMap = 
                    new WeakHashMap<Employee, EmployeeVal>();

        Employee emp = new Employee("Vinoth");
        EmployeeVal val = new EmployeeVal("Programmer");

        aMap.put(emp, val);

        emp = null;

        System.gc();
        int count = 0;
        while (0 != aMap.size()) {
            ++count;
            System.gc();
        }
        System.out.println("Took " + count
                + " calls to System.gc() to result in weakHashMap size of : "
                + aMap.size());
    }
}

输出: 20 calls to System.gc()进行了20 calls to System.gc()导致aMap size为:0。

WeakHashMap仅对键具有弱引用,而与其他Map类一样,不具有强引用。尽管已经使用过WeakHashMap但在强烈引用值或键的情况下,有些情况下您必须要小心。通过将对象包装在WeakReference 中可以避免这种情况。

import java.lang.ref.WeakReference;
import java.util.HashMap;

public class Test {

    public static void main(String args[]) {
        HashMap<Employee, EmployeeVal> map = 
                      new HashMap<Employee, EmployeeVal>();
        WeakReference<HashMap<Employee, EmployeeVal>> aMap = 
                       new WeakReference<HashMap<Employee, EmployeeVal>>(
                map);

        map = null;

        while (null != aMap.get()) {
            aMap.get().put(new Employee("Vinoth"),
                    new EmployeeVal("Programmer"));
            System.out.println("Size of aMap " + aMap.get().size());
            System.gc();
        }
        System.out.println("Its garbage collected");
    }
}

软参考。

Soft Reference比弱参考略强。软引用允许进行垃圾回收,但是乞求垃圾回收器仅在没有其他选择时才将其清除。

垃圾收集器不会像弱可访问对象那样积极地收集可软访问对象 - 而是仅在确实 “需要” 内存时才收集可软访问对象。软引用是对垃圾收集器说的一种方式,“只要内存不太紧,我想保留这个对象。但是如果内存真的很紧,那就继续收集它,我会处理接着就,随即。” 垃圾收集器需要清除所有软引用,然后才能抛出OutOfMemoryError

软引用和弱引用之间的唯一真正区别是:

垃圾回收器使用算法来决定是否回收可软访问的对象,但始终回收可弱访问的对象。

SoftReference专为缓存而设计。如果发现WeakReference引用了其他方式无法访问的对象,则它将立即被清除。 SoftReference可能保留SoftReference 。通常,存在一些与可用内存量和最后一次确定是否应清除时间有关的算法。当前的 Sun 算法将清除引用,前提是 Java 堆上没有可用的内存的秒数内没有使用该引用(可配置的服务器 HotSpot 根据-Xmx设置检查最大可能的堆)。除非抛出异常,否则在OutOfMemoryError之前将清除SoftReference

本文对于理解强,弱,弱和幻像引用可能非常有帮助。


为了给您一个总结,

如果您仅对对象具有弱引用 (而没有强引用),则该对象将在下一个 GC 周期中由 GC 回收。

如果您仅具有对对象的软引用 (没有强引用),则仅当 JVM 内存不足时,GC 才会回收该对象。


因此,您可以说强引用具有最终的力量 (GC 永远无法收集)

软引用比弱引用更强大 (因为软引用可以逃避 GC 周期,直到 JVM 内存不足)

弱引用甚至没有软引用强大 (因为它们不能排除任何 GC 周期,如果对象没有其他强引用,则将被回收)。


餐厅类比

  • 服务员 - GC
  • 您 - 堆中的对象
  • 餐厅面积 / 空间 - 堆空间
  • 新客户 - 在餐厅要桌子的新对象

现在,如果您是一个强大的客户 (类似于强大的参考客户 ),那么即使有新客户进来餐厅或发生的事情,您也永远不会离开桌子(堆上的内存区域)。服务员无权告诉您(甚至要求您)离开餐厅。

如果您是一位软顾客 (类似于软顾客 ),那么如果有新顾客进来,餐厅的服务员将不会要求您离开桌子,除非没有其他空桌子来容纳新顾客。 (换句话说,服务员只会在有新客户进场并且没有其他可供该新客户使用的桌子时才要求您离开桌子)

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

唯一真正的不同

根据文档必须通过运行中的 GC 清除宽松的 WeakReferences。

根据文档必须在抛出 OOM 之前清除松散的 SoftReferences。

那是唯一的真正区别。其他所有内容都不是合同的一部分。 (我认为最新文档具有合同效力。)

SoftReferences 很有用。内存敏感的缓存使用 SoftReferences,而不是 WeakReferences。


WeakReference 的唯一正确用法是观察 GC 运行。您可以通过创建一个新的 WeakReference 来实现此目的,该对象的对象立即超出范围,然后尝试从weak_ref.get()获取 null。当它为null ,您将了解到在此期间,GC 运行了。

至于对 WeakReference 的错误使用,列表是无止境的:

  • 一个糟糕的 hack,实现 priority-2 软引用,这样您就不必写一个, 但是它不能按预期工作,因为即使在有备用内存的情况下, 每次 GC 运行都会清除缓存。请参阅https://stackoverflow.com/a/3243242/632951以获取更多信息。 (此外,如果您需要两个以上级别的缓存优先级,那该怎么办?您仍然需要一个真正的库。)

  • 将数据与现有类的对象相关联的糟糕技巧, 但是当您的 GC 决定在创建弱引用之后决定休息时,它会造成内存泄漏(OutOfMemoryError)。此外,这并不难看:更好的方法是使用元组。

  • 一种糟糕的 hack,用于将数据与现有类的对象相关联,其中该类具有使自己无法成为子类的神经,并且用在您需要调用的现有功能代码中。在这种情况下,正确的解决方案是编辑类并使其可成为子类,或编辑函数并使其采用接口而不是类,或使用替代函数。

Java 中对象可到达性状态的六种类型 -

  1. 高度可访问的对象 - GC 将不会收集( 回收由占用的内存 )此类对象。这些可以通过根节点或另一个高度可访问的对象 (即,通过局部变量,类变量,实例变量等)来访问。
  2. LY 可及对象 - GC 可能试图收集这种依赖于内存争的对象。可以通过一个或多个软参考对象从根目录访问这些对象
  3. LY 可及对象 - GC 必须收集这类对象。这些可以通过一个或多个弱参考对象从根部到达
  4. 可复活的对象 - GC 已在收集这些对象的过程中。但是通过执行某些终结器, 它们可能会回到状态之一 - 强 / 弱 / 弱
  5. 幻影可到达的对象 - GC 已在收集这些对象,并且已确定不能由任何终结器恢复(如果它本身声明了 finalize()方法,则其终结器将已运行) 。可以通过一个或多个幻像引用对象从根目录访问这些对象
  6. 无法访问的对象 - 一个对象既不能强,不能,不能弱,也不能幻像可访问,并且不能复活。这些对象已准备好回收

有关更多详细信息: https : //www.artima.com/insidejvm/ed2/gc16.html « 合拢

应该意识到,弱引用的对象只有在只有弱引用的情况下才会被收集。如果它具有多达一个强引用,则无论它有多少弱引用都不会被收集。