java.lang.ref.WeakReference
和有什么区别java.lang.ref.SoftReference
?
12 回答
来自Ethan Nicholas 的《理解弱引用》:
弱引用
简而言之,弱引用是一种强度不足以迫使对象保留在内存中的引用。弱引用允许您利用垃圾收集器为您确定可达性的能力,因此您不必自己做。您可以像这样创建一个弱引用:
WeakReference weakWidget = new WeakReference(widget);
然后在代码的其他地方,您可以使用它
weakWidget.get()
来获取实际Widget
对象。当然,弱引用的强度不足以阻止垃圾回收,因此您可能会发现(如果没有对小部件的强引用)weakWidget.get()
突然开始返回null
....
软参考
软引用与弱引用完全一样,只是它不太急于丢弃它所引用的对象。只能弱可达的对象(对它的最强引用是
WeakReferences
)将在下一个垃圾回收周期被丢弃,但软可达的对象通常会保留一段时间。
SoftReferences
不需要与 有任何不同的行为 ,WeakReferences
但实际上只要内存充足,通常会保留软可访问的对象。这使它们成为缓存(例如上面描述的图像缓存)的绝佳基础,因为您可以让垃圾收集器担心对象的可达性(强可达性对象永远不会从缓存中删除)以及它的严重程度需要他们正在消耗的内存。
彼得凯斯勒在评论中补充道:
Sun JRE 对 SoftReference 的处理与 WeakReference 不同。如果可用内存没有压力,我们会尝试保留由 SoftReference 引用的对象。一个细节:“-client”和“-server”JRE 的策略是不同的:-client JRE 试图通过更喜欢清除 SoftReferences 而不是扩展堆来保持你的占用空间小,而 -server JRE 试图保持你的通过更喜欢扩展堆(如果可能)而不是清除 SoftReferences 来提高性能。一种尺寸并不适合所有人。
急切地收集弱引用。如果 GC 发现一个对象是弱可访问的(只能通过弱引用访问),它会立即清除对该对象的弱引用。因此,它们非常适合保留对您的程序还保留(强引用)“关联信息”的对象的引用,例如有关类的缓存反射信息或对象的包装器等。在与它关联的对象被 GC-ed 之后,没有任何意义。当弱引用被清除时,它会被排入一个引用队列,你的代码会在某个地方轮询,并且它也会丢弃关联的对象。也就是说,您保留有关对象的额外信息,但是一旦它所引用的对象消失,就不需要该信息。实际上,在某些情况下,您甚至可以继承 WeakReference 并将有关对象的相关额外信息保留在 WeakReference 子类的字段中。WeakReference 的另一个典型用途是与 Maps 结合使用以保持规范实例。
另一方面,SoftReferences 有利于缓存外部的、可重新创建的资源,因为 GC 通常会延迟清除它们。尽管可以保证在抛出 OutOfMemoryError 之前所有 SoftReference 都会被清除,因此理论上它们不会导致 OOME[*]。
典型的用例示例是保留文件中内容的解析形式。您将实现一个系统,您将在其中加载文件、解析它,并为解析表示的根对象保留一个 SoftReference。下次您需要该文件时,您将尝试通过 SoftReference 检索它。如果您可以检索到它,您就省去了另一个加载/解析,如果 GC 在此期间清除了它,您就重新加载它。这样,您可以利用空闲内存进行性能优化,但不会冒 OOME 的风险。
现在为 [*]。保持 SoftReference 本身不会导致 OOME。另一方面,如果您错误地将 SoftReference 用于要使用 WeakReference 的任务(即,您保持与某个对象关联的信息以某种方式被强引用,并在 Reference 对象被清除时丢弃它),您可能会遇到 OOME轮询 ReferenceQueue 并丢弃关联对象的代码可能碰巧没有及时运行。
因此,决定取决于使用情况 - 如果您要缓存构建成本高昂但仍可从其他数据重建的信息,请使用软引用 - 如果您要保留对某些数据的规范实例的引用,或者您想要在没有“拥有”它的情况下引用一个对象(从而防止它被 GC'd),使用弱引用。
在 Java 中;从强到弱依次为:强、软、弱、幻
强引用是保护被引用对象不被 GC 收集的普通引用。即从不垃圾收集。
软引用有资格被垃圾收集器收集,但在需要其内存之前可能不会被收集。即垃圾收集之前OutOfMemoryError
。
弱引用是不保护被引用对象不被 GC 收集的引用。即当没有强或软引用时垃圾收集。
幻影引用是对对象的引用,它在最终确定之后但在其分配的内存被回收之前被幻影引用。
类比:假设JVM是一个王国,Object是王国的国王,GC是王国的攻击者试图杀死国王(对象)。
- 当King is Strong时,GC无法杀死他。
- 当 King 是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
with 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
是一个条目 ( key-to-value mappings
) 将在不再可能从Map
.
让我用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
。
软引用和弱引用之间唯一真正的区别是
垃圾收集器使用算法来决定是否回收一个软可达对象,但总是回收一个弱可达对象。
SoftReference
专为缓存而设计。当发现 aWeakReference
引用了一个无法访问的对象时,它将立即被清除。SoftReference
可以保持原样。通常,有一些算法与可用内存量和最后用于确定是否应该清除它的时间有关。当前的 Sun 算法是在没有使用 Java 堆上的可用内存兆字节的秒数内清除引用(可配置,服务器 HotSpot 检查由 设置的最大可能堆-Xmx
)。SoftReference
s 将在OutOfMemoryError
被抛出之前被清除,除非可以访问。
这篇文章对于理解强引用、软引用、弱引用和虚引用非常有帮助。
给大家总结一下,
如果您只有对对象的弱引用(没有强引用),那么该对象将在下一个 GC 循环中被 GC 回收。
如果你只有对一个对象的软引用(没有强引用),那么只有当 JVM 内存不足时,GC 才会回收该对象。
所以你可以说,强引用具有终极力量(GC永远无法收集)
软引用比弱引用更强大(因为它们可以逃避 GC 循环,直到 JVM 内存不足)
弱引用甚至不如软引用强大(因为它们无法摆脱任何 GC 循环,并且如果对象没有其他强引用,它们将被回收)。
餐厅类比
- 服务员 - GC
- 你 - 堆中的对象
- 餐厅区域/空间 - 堆空间
- 新客户 - 想要在餐厅用餐的新对象
现在,如果您是一个强大的客户(类似于强参考),那么即使有新客户来到餐厅或发生什么事情,您也永远不会离开您的餐桌(堆上的内存区域)。服务员无权告诉您(甚至要求您)离开餐厅。
如果你是软顾客(类似于软参考),那么如果餐厅有新顾客进来,服务员不会要求你离开餐桌,除非没有其他空桌子可以容纳新顾客。(换句话说,只有当有新客户进来并且没有其他桌子可供该新客户使用时,服务员才会要求您离开餐桌)
如果您是弱客户(类似于弱参考),那么服务员可以(在任何时间点)随意要求您离开餐厅:P
Java 中的六种对象可达状态:
- 强可达对象 - GC不会收集(回收所占用的内存)这种对象。这些可以通过根节点或另一个强可达对象 (即通过局部变量、类变量、实例变量等)访问
- 可软访问的对象 - GC可能会根据内存争用尝试收集此类对象。这些可以通过一个或多个软引用对象从根访问
- 弱可达对象 - GC必须收集这种对象。这些可以通过一个或多个弱引用对象从根访问
- Resurrect-able objects - GC 已经在收集这些对象的过程中。但他们可能会通过执行一些终结器回到其中一种状态 - 强/软/弱
- 幻象可达对象 - GC已经在收集这些对象的过程中,并且已经确定任何终结器都不能复活(如果它自己声明了 finalize() 方法,那么它的终结器将已经运行)。这些可以通过一个或多个幻像引用对象从根访问
- 无法到达的对象 - 一个对象既不是强烈的、柔和的、弱的,也不是幻影可以到达的,也不是可复活的。这些对象已准备好进行回收
更多详情:https ://www.artima.com/insidejvm/ed2/gc16.html « 折叠
唯一真正的区别
根据文档,松散的 WeakReferences必须由正在运行的 GC 清除。
根据文档,必须在抛出 OOM 之前清除松散的 SoftReferences 。
这是唯一真正的区别。其他一切都不是合同的一部分。(我假设最新的文档是合同性的。)
软引用很有用。内存敏感缓存使用 SoftReferences,而不是 WeakReferences。
WeakReference的唯一正确用途是观察 GC 运行。为此,您可以创建一个新的 WeakReference,其对象立即超出范围,然后尝试从
weak_ref.get()
. 当它是 时null
,您会了解到在这段时间之间,GC 运行了。
至于 WeakReference 的错误使用,不胜枚举:
一个糟糕的 hack 来实现优先级 2 软引用,这样您就不必编写一个,但它不能按预期工作,因为缓存会在每次GC 运行时被清除,即使有空闲内存也是如此。有关 phails,请参阅 https://stackoverflow.com/a/3243242/632951。(此外,如果您需要 2 级以上的缓存优先级怎么办?您仍然需要一个真正的库。)
将数据与现有类的对象相关联的糟糕技巧,但是当您的 GC 决定在创建弱引用后休息时,它会产生内存泄漏 (OutOfMemoryError)。此外,它非常丑陋:更好的方法是使用元组。
将数据与现有类的对象相关联的糟糕技巧,其中该类有胆量使其自身不可子类化,并用于您需要调用的现有函数代码中。在这种情况下,正确的解决方案是编辑类并使其可子类化,或编辑函数并使其采用接口而不是类,或使用替代函数。
为了给出实际的内存使用方面,我做了一个实验,在重负载下使用强、软、弱和幻影引用,将它们保留到程序结束。然后监控堆使用情况和 GC 行为。这些指标可能会因具体情况而异,但肯定会提供高水平的理解。以下是调查结果。
重负载下的堆和 GC 行为
- 强/硬引用- 随着程序的继续,JVM 无法收集保留的强引用对象。最终以“java.lang.OutOfMemoryError: Java heap space”告终
- 软引用- 随着程序的继续,堆使用量不断增长,但 OLD gen GC 在接近最大堆时发生。启动程序后,GC 启动时间稍晚。
- 弱参考- 随着程序的启动,对象几乎立即开始完成并被收集。大多数对象在年轻代垃圾收集中被收集。
- 幻影引用- 与弱引用类似,幻影引用的对象也开始完成并立即进行垃圾收集。没有老年代GC,所有对象都在年轻代垃圾收集本身中被收集。
应该知道,弱引用对象只有在它只有弱引用时才会被收集。如果它只有一个强引用,那么无论它有多少弱引用,它都不会被收集。
WeakReference:在每个 GC 周期(次要或完全)收集仅被弱引用的对象。
SoftReference:何时收集仅软引用的对象取决于:
-XX:SoftRefLRUPolicyMSPerMB=N 标志(默认值为 1000,即 1 秒)
堆中的可用内存量。
例子:
- heap 有 10MB 的可用空间(full GC 之后);
- -XX:SoftRefLRUPolicyMSPerMB=1000
如果上次访问的时间大于 10 秒,则仅由 SoftReference 引用的对象将被收集。