17

我在 JVM 中运行了两个独立的缓存(一个由第三方库控制),每个缓存都使用软引用。我希望 JVM 在库控制的缓存之前清除我的受控缓存。SoftReference javadoc 指出:

在虚拟机抛出 OutOfMemoryError 之前,保证已清除对软可访问对象的所有软引用。否则,对清除软引用的时间或清除对不同对象的一组此类引用的顺序没有任何限制。但是,鼓励虚拟机实现偏向于清除最近创建或最近使用的软引用。

此类的直接实例可用于实现简单的缓存;此类或派生的子类也可用于更大的数据结构以实现更复杂的缓存。只要软引用的所指对象是强可达的,即实际在使用中,软引用就不会被清除。因此,例如,复杂的高速缓存可以通过保持对这些条目的强引用来防止其最近使用的条目被丢弃,而剩余的条目则由垃圾收集器自行决定丢弃。

常见的 JVM 实现,尤其是 HotSpot,在实践中是如何处理 SoftReference 的?他们是否像规范所鼓励的那样“倾向于清除最近创建或最近使用的软引用”?

4

5 回答 5

8

看起来它可以调整,但事实并非如此。并发标记清除收集器挂在默认堆的实现上must_clear_all_soft_refs(),显然只有true在执行_last_ditch_collection.

bool GenCollectedHeap::must_clear_all_soft_refs() {
  return _gc_cause == GCCause::_last_ditch_collection;
}

虽然失败分配的正常处理是对堆方法的三个连续调用,但do_collectCollectorPolicy.cpp

HeapWord* GenCollectorPolicy::satisfy_failed_allocation(size_t size,
                                                    bool   is_tlab) {

它尝试收集,尝试重新分配,如果失败则尝试扩展堆,然后作为最后的努力,尝试收集清除软引用。

最后一个集合的评论非常有说服力(也是唯一一个触发清除软引用的评论)

  // If we reach this point, we're really out of memory. Try every trick
  // we can to reclaim memory. Force collection of soft references. Force
  // a complete compaction of the heap. Any additional methods for finding
  // free memory should be here, especially if they are expensive. If this
  // attempt fails, an OOM exception will be thrown.
  {
    IntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted

    gch->do_collection(true             /* full */,
                       true             /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }

---针对显而易见的情况进行编辑,我描述的是弱引用,而不是软引用 ---

在实践中,我认为只有在调用 JVM 进行垃圾收集以响应他们试图避免OutOfMemoryError.

要使SoftReferences 与所有四个 Java 1.4 垃圾收集器以及新的 G1 收集器兼容,决定必须仅取决于可达性确定。等到 reaping 和 compaction 发生时,再决定一个对象是否可达已经太晚了。这表明(但不要求)存在一个集合“上下文”,它根据堆中的可用内存可用性确定可达性。这样的上下文必须SoftReference在尝试遵循它们之前表明不遵循 s。

由于OutOfMemoryError避免垃圾收集是专门以完全收集、停止世界的方式安排的,因此不难想象堆管理器SoftReference在收集发生之前设置“不遵循”标志的场景。

---好的,所以我决定“必须以这种方式工作”的答案还不够好---

来自源代码src/share/vm/gc_implementation/concurrentMarkSweep/vmCMSOperations.cpp(亮点是我的)

实际“做”垃圾收集的操作:

  170 void VM_GenCollectFullConcurrent::doit() {

我们最好是一个VM线程,否则一个“程序”线程就是垃圾收集!

  171   assert(Thread::current()->is_VM_thread(), "Should be VM thread");

我们是并发收集器,所以我们最好同时调度!

  172   assert(GCLockerInvokesConcurrent || ExplicitGCInvokesConcurrent, "Unexpected");
  173 

抓取堆(其中包含 GCCause 对象)。

  174   GenCollectedHeap* gch = GenCollectedHeap::heap();

检查我们是否需要前景“年轻”集合

  175   if (_gc_count_before == gch->total_collections()) {
  176     // The "full" of do_full_collection call below "forces"
  177     // a collection; the second arg, 0, below ensures that
  178     // only the young gen is collected. XXX In the future,
  179     // we'll probably need to have something in this interface
  180     // to say do this only if we are sure we will not bail
  181     // out to a full collection in this attempt, but that's
  182     // for the future.

程序线程不干预堆吗?

  183     assert(SafepointSynchronize::is_at_safepoint(),
  184       "We can only be executing this arm of if at a safepoint");

从堆中获取垃圾回收原因(本次回收的原因)。

  185     GCCauseSetter gccs(gch, _gc_cause);

做一个完整的年轻空间收藏

请注意,他传入了堆的 must_clear_all_soft_refs 标志的值,该标志在 OutOfMemory 场景中必须设置为 true,并且在任何一种情况下都指示“do_full_collection”不遵循软引用

  186     gch->do_full_collection(gch->must_clear_all_soft_refs(),
  187                             0 /* collect only youngest gen */);

_gc_cause 是一个枚举,它(这里是猜测)_allocation_failure在第一次尝试避免时设置为OutOfMemoryError_last_ditch_collection之后失败(尝试收集瞬态垃圾)

快速查看内存“堆”模块显示do_full_collection调用do_collection软引用被明确清除(在“正确”条件下)与行

  480   ClearedAllSoftRefs casr(do_clear_all_soft_refs, collector_policy());

--- 原帖是为那些想了解弱引用的人准备的 ---

在 Mark and Sweep 算法中,主线程遵循软引用(因此除非不同的分支可以通过非软引用到达它,否则不会被标记。

在复制算法中,软引用指向的对象不会被复制(同样,除非它们被不同的非软引用到达)。

基本上,当从“主”执行线程跟踪引用网络时,不遵循软引用。这允许它们的对象被垃圾收集,就像它们没有指向它们的引用一样。

值得一提的是,软引用几乎从不孤立地使用。它们通常用于对象中的设计是对对象有多个引用,但只需要清除一个引用即可触发垃圾收集(为了便于维护容器,或者不需要查找昂贵引用的运行时性能) .

于 2012-03-29T19:54:08.583 回答
4

在 HotSpot 常见问题解答中找到一条信息,可能已过时: http ://www.oracle.com/technetwork/java/hotspotfaq-138619.html#gc_softrefs

什么决定了何时刷新软引用的对象?

从 1.3.1 开始,软可访问对象将在最后一次被引用后保持活动一段时间。默认值是堆中每兆字节的生命周期一秒。可以使用 -XX:SoftRefLRUPolicyMSPerMB 标志调整此值,该标志接受表示毫秒的整数值。例如,要将值从 1 秒更改为 2.5 秒,请使用以下标志:

-XX:SoftRefLRUPolicyMSPerMB=2500

Java HotSpot Server VM 使用最大可能的堆大小(使用 -Xmx 选项设置)来计算剩余的可用空间。

Java Hotspot Client VM 使用当前堆大小来计算可用空间。

这意味着服务器 VM 的总体趋势是增加堆而不是刷新软引用,因此 -Xmx 对软引用何时被垃圾回收有显着影响。

另一方面,客户端虚拟机将更倾向于刷新软引用而不是增加堆。

上述行为适用于 Java HotSpot VM 的 1.3.1 到 Java SE 6 版本。但是,此行为不是 VM 规范的一部分,并且在未来的版本中可能会发生变化。同样,不保证 -XX:SoftRefLRUPolicyMSPerMB 标志存在于任何给定版本中。

在 1.3.1 版之前,Java HotSpot VM 在找到软引用时会清除它们。

更多详细信息请访问:http: //jeremymanson.blogspot.com/2009/07/how-hotspot-decides-to-clear_07.html(由 MiserableVariable 的评论提供)

于 2012-04-03T21:59:19.380 回答
3

无论答案是什么,依赖特定策略都会使您的软件变得不可靠,因为每个 JVM 实现都可能不同。即使对于给定的 JVM,以不同方式对其进行配置也可能会改变确切的策略并破坏您的软件。简而言之,依赖特定策略是错误的。

您的缓存管理什么类型的资源?如果它是一个纯堆分配的对象,那么策略应该无关紧要。不过,使用 ReferenceQueue 可能有助于在清除 SoftReference 时通知您。

如果资源类型不仅仅是堆分配的对象,那么您必须要求您的用户调用显式释放方法,即 Closeable.close()。为了防止“忘记”调用此 release 方法,您可以考虑实现 finalize() 方法,但要注意它的副作用。有关这方面的更多信息,我建议阅读 Joshua Bloch 的“Effective Java (2nd Edition)”中的“Item 7: Avoid finalizers”。

于 2012-03-29T20:19:57.727 回答
2

并不是说这是权威SoftReference的,而是愤怒地使用我从未见过 VM 来刷新它们而不是增加 VM 大小。实际上,我以某种方式认为是这种情况,并且设计在很大程度上取决于此。我确实有同样-ms的,-mx 但这不重要。

但我找不到任何实际说明这是必需的规范。这个博客似乎非常详细地介绍了如何SoftReferences刷新。从快速阅读来看,即使有其他内存可用,它们似乎也可以被清除。

于 2012-04-04T16:18:43.013 回答
0

只是头脑风暴。如果您希望您的缓存在另一个缓存之前被清除,也许您可​​以将两者联系起来?也许通过保持对第二个缓存中的条目的强引用并仅在您自己的缓存成员被清除时才释放这些引用?

看起来很绕。我可能倾向于简单地接受两个缓存都正是缓存。缓存未命中可能会给性能带来痛苦,但至少您的软件不会有复杂的缓存管理策略。

于 2012-04-03T22:19:44.613 回答