关于垃圾收集:如何在Java中清除软引用?

How to cause soft references to be cleared in Java?

我有一个缓存,其中包含对缓存对象的软引用。我正试图为类的行为编写一个函数测试,这些类专门使用缓存来测试在清除缓存对象时会发生什么。

问题是:我似乎无法可靠地清除软引用。仅仅消耗一堆内存并不能做到这一点:在清除任何软引用之前,我会从内存中得到一个内存。

有什么方法能让Java更渴望清理软引用?

在这里找到:

"It is guaranteed though that all
SoftReferences will get cleared before
OutOfMemoryError is thrown, so they
theoretically can't cause an OOME."

所以这是否意味着上面的场景一定意味着我在某个地方有内存泄漏,某个类在缓存对象上持有硬引用?


还有以下用于调整软引用处理方式的jvm参数:

1
-XX:SoftRefLRUPolicyMSPerMB=<value>

其中"value"是软引用将为每一可用MB内存保留的毫秒数。默认值是1s/mb,所以如果一个对象只能软访问,那么如果只有1MB的堆空间可用,它将持续1s。


The problem is: I can't seem to
reliably get the soft references to be
cleared.

这对于软引用来说不是唯一的。由于Java中垃圾收集的性质,不能保证任何垃圾收集的东西都会在任何时间点被收集。即使使用一个简单的代码:

1
2
3
Object temp = new Object();
temp = null;
System.gc();

不能保证在第一行中实例化的对象在这里或实际上在任何点上都是垃圾收集的。在内存管理语言中,这只是你必须面对的事情之一,你正在放弃对这些事情的声明能力。是的,这可能使得有时很难确定地测试内存泄漏。

也就是说,根据您引用的javadocs,在抛出OutofMemoryError之前,应该明确地清除SoftReferences(事实上,这是它们的全部点,也是它们与默认对象引用不同的唯一方式)。因此,听起来好像有某种内存泄漏,因为您持有对所讨论对象的更严格的引用。

如果您对JVM使用-XX:+HeapDumpOnOutOfMemoryError选项,然后将堆转储加载到jhat之类的东西中,那么您应该能够看到对对象的所有引用,从而查看软对象旁边是否有任何引用。或者,您可以在测试运行时使用分析器来实现相同的事情。


您可以使用这段代码强制清除测试中的所有软引用。


在典型的JVM实现(Sun)中,您需要多次触发完整的GC来清除软引用。这是因为软引用要求GC做更多的工作,例如,一种机制允许您在回收对象时得到通知。

imho在应用服务器中使用大量的sofreferences是邪恶的,因为开发人员无法控制它们何时被释放。


如果您真的想这样做,可以在SoftReference上调用Clear()来清除它。

也就是说,如果JVM正在抛出OutofMemoryError,而您的软引用尚未清除,那么这意味着您必须在其他地方对对象进行硬引用。否则将使软参考合同失效。否则,您永远不能保证清除SoftReference:只要仍然有可用的内存,JVM就不需要清除任何SoftReference。另一方面,允许在下次执行GC循环时清除它们,即使不需要。

另外,您可以考虑研究weakreference,因为vm在清除它们方面更具攻击性。从技术上讲,虚拟机从来没有被要求清除weakreference,但是它应该在下次执行GC循环时清除它们,如果对象否则会被认为是死的。如果您试图测试清除缓存后会发生什么,那么使用weakreferences应该可以帮助您更快地删除条目。

另外,请记住,这两者都依赖于执行GC循环的JVM。不幸的是,没有办法保证其中的一个会发生。即使您调用System.gc(),垃圾收集器也可能会认为它做得很好,并且选择什么都不做。


垃圾收集和其他引用(如软引用)是不确定的。这实际上不可能可靠地执行某些操作,因此软引用在此时被明确清除,以便测试可以判断您的carche的反应。我建议您通过模拟等更明确地模拟引用清除-您的测试将是可复制的,并且更有价值,而不仅仅是希望GC清除引用。使用后一种方法是一件非常糟糕的事情,它只会带来额外的问题,而不是帮助您提高缓存及其协作组件的质量。


缓存对象是否有终结器?终结器将创建对对象的新的强引用,因此即使清除了SoftReference,在稍后的GC循环之前也不会回收内存。


如果您使用Eclipse,那么有一个名为memory analyzer的工具可以使堆转储调试更加容易。


从文件和我的经验来看,我会说是的:你必须在其他地方有一个参考资料。

我建议使用一个调试器,它可以显示所有对对象的引用(例如,调试Java 6时的Eclipse 3.4),只需检查OOM何时被抛出。


如果您有一个缓存,它是一个软引用的映射,并且您希望清除它们,那么您只需清除()映射,它们都将被清除(包括它们的引用)。