JVM如何在实践中收集SoftReferences?

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

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

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

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

看起来它可以调整,但事实并非如此。 并发标记扫描收集器挂起在缺省堆的must_clear_all_soft_refs()实现上,这显然只在执行_last_ditch_collection时才为true

 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之前,这样的上下文必须指示不遵循SoftReference

由于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是一个枚举,在第一次尝试避免OutOfMemoryError设置为_allocation_failure ,在失败后设置为_allocation_failure (尝试收集瞬态垃圾)

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

  480 ClearedAllSoftRefs casr(do_clear_all_soft_refs, collector_policy()); 

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

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

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

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

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

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

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

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

在HotSpot FAQ中找到了一条可能已过时的信息: 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 SE6版本的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的评论提供)

并不是说这是权威的,而是在愤怒中使用SoftReference我从未见过VM来冲洗它们而不是增加VM大小。 实际上我以某种方式认为是这样的,设计非常依赖于此。 我确实有相同的-ms-mx但这应该没关系。

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

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

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