繁体   English   中英

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

[英]What's the difference between SoftReference and WeakReference in Java?

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

理解弱引用,伊桑尼古拉斯:

弱引用

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

 WeakReference weakWidget = new WeakReference(widget);

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

...

软引用

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

SoftReferences的行为不需要WeakReferences任何不同,但在实践中,只要内存充足,通常会​​保留软可访问对象。 这使它们成为缓存的绝佳基础,例如上述图像缓存,因为您可以让垃圾收集器担心对象的可达性(强可达对象永远不会从缓存中删除)以及它的严重程度需要他们消耗的内存。

彼得凯斯勒在评论中补充道:

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

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

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

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

现在是 [*]。 保持 SoftReference 本身不会导致 OOME。 另一方面,如果您错误地将 SoftReference 用于要使用 Wea​​kReference 的任务(即,您以某种方式保持与对象关联的信息以某种方式强引用,并在引用对象被清除时丢弃它),您可能会遇到 OOME轮询 ReferenceQueue 并丢弃关联对象的代码可能没有及时运行。

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

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

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

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

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

幻影引用是对对象的引用在对象完成之后但在其分配的内存被回收之前被幻影引用。

来源

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

  • 当King is Strong时,GC杀不了他。
  • 当 King is Soft 时,GC 攻击他,但 King 会保护王国,直到资源可用。
  • 当国王很弱时,GC攻击他但在没有保护的情况下统治了王国。
  • 当king是Phantom时,GC已经杀了他,但是king可以通过他的灵魂获得。

弱参考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是一种当不再可能从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可以保持原样。 通常有一些与可用内存量和上次使用的时间相关的算法来确定是否应该清除它。 当前的 Sun 算法是清除引用,如果它在 Java 堆上有兆字节可用内存的秒数内没有被使用(可配置,服务器 HotSpot 检查由-Xmx设置的最大可能堆)。 SoftReference s 将在OutOfMemoryError之前被清除,除非以其他方式可达。

这篇文章对于理解强引用、软引用、弱引用和幻像引用非常有帮助。


给你一个总结,

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

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


所以你可以说,强引用拥有终极力量(永远不会被GC收集)

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

弱引用甚至不如软引用强大(因为它们不能超越任何 GC 循环,并且如果对象没有其他强引用就会被回收)。


餐厅类比

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

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

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

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

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

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

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

唯一真正的区别

根据文档,松散的 WeakReferences必须由正在运行的 GC 清除。

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

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

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


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

至于 WeakReference 的错误使用,不胜枚举:

  • 实现优先级 2 软引用的糟糕方法,这样您就不必编写一个,它无法按预期工作,因为每次GC 运行时都会清除缓存,即使有空闲内存。 有关 phails,请参阅https://stackoverflow.com/a/3243242/632951 (此外,如果您需要 2 级以上的缓存优先级怎么办?您仍然需要一个真正的库。)

  • 一个糟糕的黑客攻击,与现有的类的对象相关联的数据,它会创建一个内存泄漏(OutOfMemoryError错误)当你的GC决定创建你在WeakReferences后稍事休息。 此外,它非常丑陋:更好的方法是使用元组。

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

为了给出实际内存使用情况,我在重载重物的情况下对强、软、弱和幻象引用进行了实验,将它们保留到程序结束。 然后监视堆使用和 GC 行为 这些指标可能因情况而异,但肯定会提供高层次的理解。 以下是调查结果。

重负载下的堆和 GC 行为

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

您可以在此处获得有关此实验的更多深度图表、统计数据和观察结果

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

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

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

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

  2. 堆中的空闲内存量。

    例子:

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM