Java中的SoftReference和WeakReference有啥区别?

Posted

技术标签:

【中文标题】Java中的SoftReference和WeakReference有啥区别?【英文标题】:What's the difference between SoftReference and WeakReference in Java?Java中的SoftReference和WeakReference有什么区别? 【发布时间】:2010-09-22 21:57:49 【问题描述】:

java.lang.ref.WeakReferencejava.lang.ref.SoftReference 有什么区别?

【问题讨论】:

SoftReferences 是 WeakReferences 的类型(不是真的,只是为了讨论),通常在 JVM 认为内存不足时收集。 @AjeetGanga,只要 GC 运行,就会始终收集松散的弱引用。见***.com/a/46291143/632951 【参考方案1】:

Java 中的六种对象可达状态:

    可达对象 - GC 不会收集(回收占用的内存)这种对象。它们可通过根节点或其他强可达对象访问(即通过局部变量、类变量、实例变量等) 可访问的对象 - GC 可能会尝试根据内存争用来收集此类对象。这些可以通过一个或多个软引用对象从根目录访问 可到达对象 - GC 必须收集此类对象。这些 可以通过一个或多个弱引用对象从根访问 Resurrect-able 对象 - GC 已经在收集这些对象。但是他们可能会通过执行一些终结器回到其中一种状态 - 强/软/弱 幻影可到达的对象 - GC 已经在收集这些对象的过程中并且已经确定不能被任何终结器复活(如果它自己声明了一个 finalize() 方法,那么它的终结器将已运行)。这些可以通过一个或多个幻像引用对象从根目录访问 Unreachable 对象 - 一个对象既不能强、弱、弱,也不能幻影可及,也不能复活。这些对象已准备好回收

更多详情:https://www.artima.com/insidejvm/ed2/gc16.html«折叠

【讨论】:

对幻像引用的描述不是很好。此外,您以特殊的顺序列出了 4 种类型。 “幻影”是最弱的类型,不是最强的类型。列出这些的传统顺序是“强、软、弱、幻”。而且我不知道您从哪里得到幻像对象用于缓存机制的概念。 AFAIK,它们是只有 GC 才能看到的临时状态,不是普通程序员可以使用的。 @ToolmakerSteve 和所有 - 为几件事道歉 1. 在我的答案的先前版本中对 Phantom 引用的错误解释,以及 2. 纠正错误的延迟。现在已通过纠正错误改进了答案【参考方案2】:

为了给出实际的内存使用情况,我做了一个实验,在重负载下使用强、软、弱和幻影引用,将它们保留到程序结束。然后监控堆使用和 GC 行为。这些指标可能会因具体情况而异,但肯定会提供高水平的理解。以下是调查结果。

重负载下的堆和 GC 行为

强/硬引用 - 随着程序的继续,JVM 无法收集保留的强引用对象。最终出现在“java.lang.OutOfMemoryError: Java heap space” 软引用 - 随着程序的继续,堆使用量不断增长,但 OLD gen GC 在接近最大堆时发生。启动程序后,GC 开始的时间稍晚。 弱参考 - 随着程序的启动,对象几乎立即开始完成并被收集。大多数对象在年轻代垃圾回收中被回收。 幻影引用 - 与弱引用类似,幻影引用的对象也开始完成并立即进行垃圾收集。没有老年代 GC,所有对象都在年轻代垃圾收集本身中被收集。

您可以更深入地了解graphs, stats, observations for this experiment here。

【讨论】:

【参考方案3】:

来自Understanding Weak References,作者:Ethan Nicholas:

弱引用

弱引用,简单来说就是 不够强大的参考 强制对象保留在内存中。 弱引用允许您利用 垃圾收集器的能力 确定您的可达性,因此您 不必自己做。你 像这样创建一个弱引用:

WeakReference weakWidget = new WeakReference(widget);

然后 您可以在代码中的其他地方使用 weakWidget.get() 获取实际 Widget 对象。当然弱 参考不够强大 防止垃圾收集,所以你可以 找到(如果没有强 对小部件的引用) weakWidget.get()突然开始 返回null

...

软参考

软引用与 弱参考,除了它更少 急于把物体扔掉 它所指的。一个对象是 只有微弱可达(最强 对它的引用是WeakReferences) 将在下一次垃圾中丢弃 收集周期,但一个对象 是轻而易举的,通常会 停留一段时间。

SoftReferences 不是必需 行为不同于 WeakReferences,但在实践中轻声细语 可达对象通常是 只要记忆还在 货源充足。这使他们成为 缓存的良好基础,例如 作为上述的图像缓存, 因为你可以让垃圾 收藏家担心两者如何 可到达的对象是(强烈的 可达对象将永远被移除 从缓存中)以及它的需要程度 他们正在消耗的内存。

Peter Kessler 在评论中补充道:

Sun JRE 对 SoftReference 的处理方式与 WeakReference 的处理方式不同。如果可用内存没有压力,我们会尝试保留由 SoftReference 引用的对象。一个细节:“-client”和“-server”JRE 的策略是不同的:-client JRE 试图通过更喜欢清除 SoftReferences 而不是扩展堆来保持你的占用空间小,而 -server JRE 试图保持你的通过更喜欢扩展堆(如果可能)而不是清除 SoftReferences 来提高性能。一种尺寸并不适合所有人。

【讨论】:

没有更多可用的帖子,您可以在回程机器上找到它:web.archive.org/web/20061130103858/http://weblogs.java.net/blog/… 这一次,存档不再可用 很好的解释。这个问题可以出现在 Java 相关的工作面试中。【参考方案4】:

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

垃圾收集器使用算法来决定是否 回收一个轻柔可达的对象,但总是回收一个弱 可到达的对象。

【讨论】:

@ATorras,萨米尔。我在这里扩展了这个答案:***.com/a/46291143/632951【参考方案5】:

这个article 对理解强引用、软引用、弱引用和虚引用非常有帮助。


给你一个总结,

如果您对一个对象只有弱引用(没有强引用),那么该对象将在下一个 GC 循环中被 GC 回收。

如果你对一个对象只有软引用(没有强引用),那么只有当 JVM 内存不足时,该对象才会被 GC 回收。


所以你可以这么说,强引用具有终极力量(永远不能被 GC 收集)

软引用比弱引用强大(因为它们可以逃避 GC 循环,直到 JVM 内存不足)

弱引用甚至不如软引用强大(因为它们无法摆脱任何 GC 周期,并且如果对象没有其他强引用,它们将被回收)。


餐厅类比

服务员 - GC 你 - 堆中的对象 餐厅区域/空间 - 堆空间 新客户 - 想要在餐厅用餐的新对象

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

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

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

【讨论】:

【参考方案6】:

WeakReference:在每个 GC 周期(次要或完整)中收集仅被弱引用的对象。

SoftReference:何时收集仅软引用的对象取决于:

    -XX:SoftRefLRUPolicyMSPerMB=N 标志(默认值为 1000,即 1 秒)

    堆中的可用内存量。

    例子:

    堆有 10MB 的可用空间(在完全 GC 之后); -XX:SoftRefLRUPolicyMSPerMB=1000

    如果上次访问的时间大于 10 秒,则仅由 SoftReference 引用的对象将被收集。

【讨论】:

【参考方案7】:

唯一真正的区别

根据the doc,松散的弱引用必须由运行中的 GC 清除。

根据the doc,松散的软引用必须在引发 OOM 之前清除。

这是唯一真正的区别。其他一切都不是合同的一部分。 (我假设最新的文档是合同性的。)

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


WeakReference 的唯一正确使用是观察 GC 运行。为此,您可以创建一个新的 WeakReference,其对象立即超出范围,然后尝试从 weak_ref.get() 中获取 null。当它是null 时,您会了解到在这段时间之间,GC 运行了。

至于不正确使用WeakReference,不胜枚举:

一个糟糕的 hack 来实现优先级 2 软引用,这样您就不必编写一个,然而它不能按预期工作,因为缓存将在 每次 GC 运行,即使有空闲内存。请参阅https://***.com/a/3243242/632951 了解 phails。 (此外,如果您需要 2 级以上的缓存优先级怎么办?您仍然需要一个真正的库。)

将数据与现有类的对象关联的糟糕技巧,然而当您的 GC 决定在创建弱引用后休息时,它会造成内存泄漏 (OutOfMemoryError)。此外,它非常丑陋:更好的方法是使用元组。

将数据与现有类的对象相关联的糟糕技巧,其中类有胆量使其自身不可子类化,并用于现有功能代码需要打电话。在这种情况下,正确的解决方案是编辑类并使其可子类化,或者编辑函数并使其采用接口而不是类,或者使用替代函数。

【讨论】:

如果缓存中键类型的equals() 只是对象标识呢?软引用在那里似乎是一种浪费,因为一旦一个关键对象不再是强可达的,就没有人会再次查找该映射。 我不同意。当您不想以任何方式影响 GC 时使用 Wea​​kReference(您可能希望保存一个对象引用,然后稍后检查它是否仍然存在,而不需要任何偏好)。如果您想影响 GC 以尝试保留对象(即,当您希望 GC 保留它时),请使用 SoftReference。 android 的 AsyncTask 中使用 Wea​​kReference 的一个很好的例子 - 保留上下文的实例。这样,如果上下文消失(如果活动 - 屏幕旋转等),AsyncTask 将不会对它有任何强引用,因此它可以被垃圾收集。检查youtu.be/…【参考方案8】:

在 Java 中;从强到弱依次为:强、软、弱、幻

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

软引用有资格被垃圾收集器收集,但可能在需要其内存之前不会被收集。即在OutOfMemoryError 之前收集垃圾。

弱引用是不保护被引用对象不被 GC 收集的引用。即当没有强或软引用时垃圾收集。

幻像引用是对对象的引用,它在最终确定之后但在其分配的内存被回收之前被幻像引用。

Source

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

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

【讨论】:

软引用 ...until memory is available 没有意义。你的意思是is eligible for collection by garbage collector, but probably won't be collected until its memory is needed for another use 是的,垃圾收集器在内存可用之前不会收集引用。 我喜欢简单解释的东西,没有太多的 bla bla bla +1 ! 优秀的总结和创新的例子 +1,进一步阅读:javarevisited.blogspot.in/2014/03/…【参考方案9】:

应该知道,弱引用对象只有在只有弱引用时才会被收集。如果它只有一个强引用,那么无论它有多少弱引用,它都不会被收集。

【讨论】:

这是常识...... softref 和 phantomref 也是如此。【参考方案10】:

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

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

让我们看看下面的例子:我们有一个 Map 与 Objects 其中 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() 得到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

【讨论】:

您可能会在aMap.get().put(...) 中获得NullPointerException 您的第一个 HashMap 示例看起来不对。当您执行“aMap.put(emp, val);”时'emp' 和 'val' 都是强引用。在内部,会创建一个新变量来保存“emp”和“val”,因此当您执行“emp = null;”时您只是在使“emp”变量无效,而不是哈希映射内部的变量(它仍保留原始 Employee 对象)。因此,无论您对外部的 'emp' 变量做什么,散列映射仍将持有对 'emp' 的强引用。 @Tiago。不。大概通过“第一个示例”,您指的是WeakHashMap 示例(因为这是第一个展示弱行为的示例)。查看“WeakHashMap”的文档:"An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. " 使用 Wea​​kHashMap 的全部意义在于 不必声明/传入 WeakReference; WeakHashMap 在内部为您执行此操作。 docs.oracle.com/javase/7/docs/api/java/util/WeakHashMap.html 对 System.gc() 进行了 0 次调用,导致weakHashMap 大小为:0 是您的第二个程序的输出? 有关WeakHashMap 的另一个示例,示例应用程序显示如何仅在执行垃圾收集之后删除条目,请参阅问题my Answer @987654324 @.【参考方案11】:

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

【讨论】:

但在 Android 中不建议缓存 developer.android.com/reference/java/lang/ref/… @DoctororDrive tbf 问题是关于 java,而不是 dalvik! :-P @YaroslavMytkalyk,坦率地说,如果 Android 想要重写一个类的行为,它应该使用自己的命名空间,而不是 java.lang。这种滥用同义词对任何人都没有好处。【参考方案12】:

急切地收集弱引用。如果 GC 发现一个对象是 弱可达(只能通过弱引用访问),它会清除 立即对该对象的弱引用。因此,它们有利于 保留对您的程序也保留的对象的引用 (强烈引用)“相关信息”,比如缓存 关于类的反射信息,或对象的包装器等。 在关联的对象之后保留任何没有意义的东西 with 是 GC-ed。当弱引用被清除时,它被排入队列 您的代码在某处轮询的引用队列,它会丢弃 关联的对象也是如此。也就是说,您保留有关 对象,但是一旦它引用的对象就不需要该信息 消失了。实际上,在某些情况下,您甚至可以子类化 弱引用并保留有关对象的相关额外信息 在 WeakReference 子类的字段中。另一个典型的使用 WeakReference 与 Maps 结合使用以保持规范实例。

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

典型的用例示例是从 文件。您将实现一个系统,您将在其中加载文件、解析它并保留 对已解析表示的根对象的 SoftReference。下次 您需要该文件,您将尝试通过 SoftReference 检索它。如果 你可以检索它,你省去了另一个加载/解析,如果 GC 同时清除它,您重新加载它。这样,您可以免费使用 内存用于性能优化,但不要冒 OOME 的风险。

现在是 [*]。保持 SoftReference 本身不会导致 OOME。如果 另一方面,您错误地将 SoftReference 用于表示 WeakReference 的任务 要使用(即,您以某种方式保留与对象关联的信息 强引用,并在 Reference 对象获取时将其丢弃 清除),您可以在轮询 ReferenceQueue 的代码中运行 OOME 并丢弃关联的对象可能碰巧没有及时运行 时尚。

因此,决定取决于使用情况 - 如果您正在缓存构建成本高昂的信息,但是 尽管如此,可以从其他数据中重建,使用软引用 - 如果您保留对某些数据的规范实例的引用,或者 你想引用一个对象而不“拥有”它(因此 防止它被GC'd),使用弱引用。

【讨论】:

对于解释何时使用弱对象特别有用。 关于正确使用WeakReference 的一个关键点是,在应该使用它的地方,在引用超出范围后,它可能会在一段时间内保持有效这一事实可能可以容忍,但不可取。 如果 WeakHashMap 总是对其键值对象产生弱引用,我很难理解它的用途是什么? @Pacerier:那篇文章的作者完全错了。他忽略了一些其他的使用场景,比如事件订阅,他的第二点是无意义的,他的第三点假设程序员可以做一些不可能的事情。他的第一点是有道理的,但与我所说的直接相关。例如,如果代码经常需要构建和比较大型不可变对象,那么构建部分通常会更便宜,如果代码创建新对象而不考虑它们是否已经存在,而是比较一个对象和本身(相同的引用)将是... ...比保存相同数据的两个不同的不可变对象之间的比较便宜得多。如果代码需要一个与现有对象匹配的对象,那么如果这些对象最终会被比较,那么使用对现有对象的引用(而不是新对象)将很有帮助。但是,如果对象存在于内部缓存中并且没有其他地方存在,则返回对它的引用将比创建新对象并返回对它的引用更昂贵,并且没有任何优势。

以上是关于Java中的SoftReference和WeakReference有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

java中的强引用(Strong reference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)

深入理解StrongReference,SoftReference, WeakReference和PhantomReference

Java 中 WeakReference 与 SoftReference 的区别?

java中SoftReference与WeakReference应用于高速缓存示例

Java SoftReference优先级

jdk源码解析--WeakReference/softReference类