一个弱哈希映射如何知道需要进行垃圾回收?

15

我最近了解到Java中的 WeakHashMap 数据结构。

然而,我不理解当它不再被常规使用时,它如何通过垃圾回收来清除映射。这个数据结构如何知道我在程序中不再使用键?如果我很长时间没有引用一个键会怎样呢?


1
有关何时使用这些“弱”数据结构的详细信息,请参见此问题 - Evan Mulawski
1
WeakHashMap并不知道如何垃圾回收任何东西,垃圾回收器才是。WeakHashMap所添加的只是使用弱引用和引用队列。 - user207421
4个回答

20
然而,我不明白当它在普通使用中不再被使用时,它是如何进行映射的垃圾回收的。
好的。在正常情况下,当垃圾收集器运行时,它会删除程序不再使用的对象。技术术语称之为“不可达对象”,这意味着程序执行不再能通过引用获取对象。一个不可达对象,可能会在下一个GC周期中被收集,也可能不会。无论哪种方式,它都不再是应用程序的关注点。
在这种情况下,WeakHashMap使用一个名为WeakReference的特殊类来引用键。弱引用是一种像间接指针(指向持有指针的对象)的对象。它具有有趣的属性,即垃圾收集器可以中断引用;即用null替换其包含的引用。规则是对于一个对象的弱引用,在GC注意到该对象不再可通过正常(强)或软引用链路到达时将被中断。
短语“不再在普通使用中”实际上意味着,键对象不再是强引用或软引用; 即不再可通过强和/或软引用链路到达。
“数据结构如何知道我在程序中不再使用键?” WeakHashmap并不知道。相反,是垃圾收集器注意到该键不再是强引用。
作为其正常遍历的一部分,GC会查找并标记所有强引用对象。然后它遍历所有WeakReference对象,并检查是否可以通过强引用或软引用链路到达对象。 如果是,则将其保留为键。否则,如果只有弱引用与键相关联,则垃圾收集器将回收该键。它所引用的对象已经被标记,如果它们没有被标记,则会将其断开。 (或者类似这样...我从未查看过实际的GC实现。而且事实上还要处理SoftReferencePhantomReference对象,这使情况变得更加复杂。)

WeakHashmap 唯一的涉及是:

  • 创建并使用WeakReference对象作为键,并
  • 清除由GC清除其弱引用键的哈希表条目。

如果长时间不引用键会发生什么?

决定打破弱引用的标准不是基于时间的。

但是时间可能会影响是否删除键。例如,一个键可以1)不再被强引用,2)从映射中检索出来,3)分配给可达变量,使其再次成为强引用。如果GC不在键不强可达的窗口期运行,那么该键及其关联值将保留在映射中。(这就是您想要发生的...)


1- 实现细节:在最近的Java版本中,弱引用实际上指的是映射的内部Entry对象,而不是键。这样可以更有效地从映射中清除失效的引用。详见代码。
2- 软引用是一种GC允许在堆内存短缺时打破的引用类型。


4
Java有一套引用系统,语言可以告诉你的代码某个对象是否仍在使用中。您可以使用引用来检测某些对象是否已被明确标识为不再使用或可用,并相应地采取措施。如果您想知道如何使用它们,本教程会深入介绍引用。
内部,WeakHashMap可能使用这些引用自动检测给定的键不能再使用。然后,实现可以将这些对象从哈希表中删除,以便它们不再占用任何空间。
希望这可以帮助您!

4
JVM垃圾回收的顺序如下:
  1. 未被引用的对象
  2. 唯一引用是WeakReference的对象
  3. 唯一引用是SoftReference的对象
通常情况下,垃圾回收器只会回收未被引用的对象。
对于一个对象的弱引用,在垃圾回收器看来并不算作引用。垃圾回收器可能会回收它们,也可能不会。通常情况下,只有在内存不足时才会回收它们,但不能保证。
如果JVM即将耗尽内存,垃圾回收器将回收软引用的对象。在回收软引用的对象之前,所有弱引用的对象都会被回收。
根据SoftReference的javadoc:

在虚拟机抛出OutOfMemoryError之前,所有对可达对象的软引用都已被清除。


2
我理解您的问题是关于“regular use”这个术语的具体含义,因此我假设您已经了解强引用和弱引用。"Regular use"指的是弱哈希映射包含的键也被其他数据结构强引用的情况。存在至少一个对键的强引用便是“regular use”。只要其他数据结构仍然引用着该键,它就不能被垃圾回收。当其他数据结构不再可达(指向其的指针不存在)时,该键也变得不可达。因为现在唯一引用该键的方式是在映射中的弱引用,所以它不再处于“regular use”状态。垃圾回收器可以最终回收它并删除映射。
以下情况需要使用到该技术:希望通过子类扩展类型C,但无法这样做的情况,例如C是具有许多实现的接口。您可以通过使用键类型为C和包装在新类E中添加的新字段的弱哈希映射来解决此问题。每当创建C实例时,也会创建E实例并将键值对添加到映射中,然后在C实例的生命周期内使用该映射来访问新字段。当C实例成为垃圾时,映射和E实例也会被回收,这是自动完成的,因为哈希映射是弱引用。如果不是弱引用,就必须像在没有垃圾回收器的编程语言中一样手动清理它。

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