我在JVM中运行两个单独的缓存(一个由第三方库控制),每个缓存都使用软引用。我希望JVM在由库控制的缓存之前清除我的受控缓存。 SoftReference javadoc声明:
保证对软可达对象的所有软引用都有 在虚拟机抛出OutOfMemoryError之前已被清除。 否则,对软件的时间没有限制 参考将被清除或一组这样的顺序 将清除对不同对象的引用。虚拟机 但是,鼓励实施偏向于清算 最近创建或最近使用的软参考。
此类的直接实例可用于实现简单缓存; 此类或派生的子类也可用于较大的数据 用于实现更复杂的缓存的结构。只要了 软引用的引用是强可达的,也就是说 实际使用中,软参考不会被清除。这样一个 例如,复杂的缓存可以防止它最近使用 通过保持强烈的指称对象而被丢弃的条目 条目,剩下的条目将被丢弃 垃圾收集者的自由裁量权。
常见的JVM实现(尤其是HotSpot)如何在实践中处理SoftReferences?他们是否“反对清除最近创建或最近使用的软参考”,这是由规范所鼓励的?
答案 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_failure
和OutOfMemoryError
失败后尝试收集瞬态垃圾,这是(此处猜测)设置为_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)
只是头脑风暴。如果您希望在其他缓存之前清除缓存,也许您可以链接这两个缓存?也许通过保留对第二个缓存中的条目的强引用,并且仅在清除自己的缓存的成员时释放这些引用?
似乎很复杂。我可能会倾向于简单地接受两个缓存都是一个缓存。缓存未命中可能会对性能造成痛苦,但至少您的软件不会有复杂的缓存管理策略。