JVM如何在实践中收集SoftReferences?

时间:2012-03-29 19:47:02

标签: java caching jvm soft-references

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

  

保证对软可达对象的所有软引用都有   在虚拟机抛出OutOfMemoryError之前已被清除。   否则,对软件的时间没有限制   参考将被清除或一组这样的顺序   将清除对不同对象的引用。虚拟机   但是,鼓励实施偏向于清算   最近创建或最近使用的软参考。

     

此类的直接实例可用于实现简单缓存;   此类或派生的子类也可用于较大的数据   用于实现更复杂的缓存的结构。只要了   软引用的引用是强可达的,也就是说   实际使用中,软参考不会被清除。这样一个   例如,复杂的缓存可以防止它最近使用   通过保持强烈的指称对象而被丢弃的条目   条目,剩下的条目将被丢弃   垃圾收集者的自由裁量权。

常见的JVM实现(尤其是HotSpot)如何在实践中处理SoftReferences?他们是否“反对清除最近创建或最近使用的软参考”,这是由规范所鼓励的?

5 个答案:

答案 0 :(得分:7)

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

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

虽然正常处理失败的分配有三次连续调用堆的do_collect方法,但CollectorPolicy.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。

要使SoftReference与所有四个Java 1.4垃圾收集器兼容,并与新的G1收集器兼容,决策必须仅在于确定可达性。当收割和压缩发生时,判断对象是否可达是为时已晚。这表明(但不要求)存在集合“上下文”,其基于堆中的可用内存可用性来确定可达性。在尝试遵循SoftReference之前,这样的背景必须表明不遵循OutOfMemoryError

由于SoftReference避免垃圾收集是以完全集合,世界各地的方式特别安排的,因此堆管理器设置“不跟随{{”的情况不难想象。 1}}“收集前的标志。

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

来自源代码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_failureOutOfMemoryError失败后尝试收集瞬态垃圾,这是(此处猜测)设置为_last_ditch_collection

快速查看memory "heap" module表示,在do_full_collection中调用do_collection软引用的行显式清除(在“正确”条件下)行

  480   ClearedAllSoftRefs casr(do_clear_all_soft_refs, collector_policy());

---对于那些想要了解弱引用的人来说,原帖如下---

在标记和扫描算法中,软引用从主线程开始是而不是(因此除非另一个分支可以通过非软引用到达它,否则不会标记。

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

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

重要的是要提到软引用几乎永远不会单独使用。它们通常用于设计要对对象进行多次引用的对象,但只需要清除一个引用来触发垃圾回收(为了便于维护容器,或者运行时不需要查找昂贵的引用)

答案 1 :(得分:4)

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

  

什么决定何时刷新轻柔引用的对象?

     

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

     

-XX:SoftRefLRUPolicyMSPerMB = 2500

     

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

     

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

     

这意味着服务器VM的总体趋势是增长   堆而不是刷新软引用,因此-Xmx有一个   对软件引用进行垃圾收集的重要影响。

     

另一方面,客户端VM将更容易刷新   软引用而不是堆积。

     

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

     

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

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

答案 2 :(得分:3)

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

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

如果资源类型不仅是堆分配的对象,则必须要求用户调用显式释放方法,即Closeable.close()。为了防止对此发布方法的“遗忘”调用,您可以考虑实现finalize()方法,但要注意其副作用。有关这方面的更多信息,我建议您阅读Joshua Bloch的“Effective Java(第2版)”中的“第7项:避免终结者”。

答案 3 :(得分:2)

并非这是权威的,但在愤怒中使用SoftReference我从未见过VM来刷新它们而不是增加VM大小。实际上我不知何故假设是这样的,设计非常依赖于此。我确实有同样的-ms-mx,但这无关紧要。

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

答案 4 :(得分:0)

只是头脑风暴。如果您希望在其他缓存之前清除缓存,也许您可​​以链接这两个缓存?也许通过保留对第二个缓存中的条目的强引用,并且仅在清除自己的缓存的成员时释放这些引用?

似乎很复杂。我可能会倾向于简单地接受两个缓存都是一个缓存。缓存未命中可能会对性能造成痛苦,但至少您的软件不会有复杂的缓存管理策略。