java优雅地完成SoftReference引用
我使用的是一个搜索库,它建议保持搜索句柄对象的打开状态,因为这样可以使查询缓存受益。随着时间的推移,我观察到缓存趋于膨胀(几百兆并不断增长),OOM开始启动。无法强制执行此缓存的限制,也无法计划它可以使用多少内存。所以我增加了Xmx限制,但这只是问题的临时解决方案
最终我想把这个对象变成^{
我所看到的关于软引用的唯一问题是,没有一种干净的方式来最终确定它们的引用。在我的例子中,在销毁搜索句柄之前,我需要关闭它,否则系统可能会耗尽文件描述符。显然,我可以将这个句柄包装到另一个对象中,在其上编写终结器(或者挂接到ReferenceQueue/PhantomReference)并释放它。但是,这个星球上的每一篇文章都建议不要使用终结器,尤其是用来释放文件句柄的终结器(例如,Effective Javaed.II,第27页)
所以我有点困惑。我是否应该小心地忽略所有这些建议,继续下去。否则,还有其他可行的替代方案吗?提前谢谢
编辑#1:下面的文本是在测试了Tom Hawtin建议的一些代码后添加的。在我看来,要么是建议不起作用,要么是我遗漏了什么。以下是代码:
class Bloat { // just a heap filler really
private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;
private final int ii;
public Bloat(final int ii) {
this.ii = ii;
}
}
// as recommended by Tom Hawtin
class MyReference<T> extends SoftReference<T> {
private final T hardRef;
MyReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.hardRef = referent;
}
}
//...meanwhile, somewhere in the neighbouring galaxy...
{
ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
int i=0;
while(i<50000) {
// set.add(new MyReference<Bloat>(new Bloat(i), rq));
set.add(new SoftReference<Bloat>(new Bloat(i), rq));
// MyReference<Bloat> polled = (MyReference<Bloat>) rq.poll();
SoftReference<Bloat> polled = (SoftReference<Bloat>) rq.poll();
if (polled != null) {
Bloat polledBloat = polled.get();
if (polledBloat == null) {
System.out.println("is null :(");
} else {
System.out.println("is not null!");
}
}
i++;
}
}
如果我用-Xmx10m
和软引用(如上面的代码)运行上面的代码段,我会打印出大量的is null :(
。但是如果我用MyReference
替换代码(用MyReference取消两行的注释,用SoftReference注释掉一行),我总是会得到OOM
正如我从建议中了解到的,在MyReference
内有硬引用不应该阻止对象击中ReferenceQueue
,对吗
# 1 楼答案
@Paul-非常感谢您的回答和澄清
@Ran-我认为在你当前的代码中,循环的末尾缺少了I++。而且,你不需要做rq。在循环中删除()作为rq。poll()已经删除了顶级引用,不是吗
几点:
1)我必须添加线程。在i++进入循环后的sleep(1)语句(对于Paul和Ran的两种解决方案),以避免OOM,但这与全局无关,也取决于平台。我的机器有一个四核CPU,运行的是Sun Linux 1.6.0_16 JDK
2)看过这些解决方案后,我想我会坚持使用终结器。布洛赫的书提供了以下理由:
仅仅为了一项看似简单的任务就必须建造大量脚手架,这在我看来是不合理的。 我的意思是,从字面上说,WTF每分钟的速率对于任何其他查看此类代码的人来说都是相当高的
3)可悲的是,保罗、汤姆和兰之间没有办法分得分数:( 我希望汤姆不会介意,因为他已经得到了很多答案:)在保罗和兰之间做出判断要困难得多——我认为两个答案都是正确的。我只是将accept flag设置为Paul的答案,因为它的评分更高(并且有更详细的解释),但Ran的解决方案一点也不差,如果我选择使用软引用实现它,它可能是我的选择。谢谢大家
# 2 楼答案
Tom的答案是正确的,但是添加到问题中的代码与Tom提出的不同。汤姆的提议更像这样:
请注意,最大的区别在于硬引用指向需要关闭的对象。周围的对象可以,也将被垃圾收集,所以你不会撞到OOM,但是你仍然有机会关闭引用。一旦你离开循环,它也会被垃圾收集。当然,在现实世界中,您可能不会使
res
成为公共实例成员这就是说,如果您持有打开的文件引用,那么在内存耗尽之前,您就面临着耗尽这些引用的风险。您可能还希望有一个LRU缓存,以确保在空中保存的文件不超过个500个。它们也可以是MyReference类型,以便在需要时也可以进行垃圾收集
为了稍微说明MySoftBloatReference是如何工作的,基类SoftReference仍然持有对占用所有内存的对象的引用。这是需要释放的对象,以防止OOM发生。但是,如果对象被释放,您仍然需要释放Bloat正在使用的资源,也就是说,Bloat正在使用两种类型的资源,内存和文件句柄,这两种资源都需要被释放,或者一种或另一种资源用完。SoftReference通过释放该对象来处理对内存资源的压力,但是您还需要释放另一个资源,即文件句柄。由于Bloat已被释放,我们无法使用它来释放相关资源,因此MySoftBloatReference保留了对需要关闭的内部资源的硬引用。一旦被告知Bloat已被释放,即一旦引用出现在ReferenceQueue中,MySoftBloatReference也可以通过其拥有的硬引用关闭相关资源
编辑:更新了代码,以便在放入类时进行编译。它使用StringReader来说明如何关闭读卡器的概念,该读卡器用于表示需要释放的外部资源。在这种特殊情况下,关闭该流实际上是一种不操作,因此不需要,但它展示了如何在需要时这样做
# 3 楼答案
啊
(据我所知)你不能两手空空。你要么保留你的信息,要么放手
然而您可以保留一些关键信息,以便最终确定。当然,关键信息必须比“真实信息”小得多,并且不能在其可到达的对象图中包含真实信息(弱引用可能会有帮助)
以现有示例为基础(注意关键信息字段):
编辑:
我想详细说明一下“要么保留信息,要么放手”。假设你有办法掌握你的信息。这将迫使GC取消标记您的数据,导致只有在您处理完数据后,才在第二个GC周期内真正清理数据。这是可能的——这正是finalize()的用途。由于您声明不希望第二个周期发生,因此无法保存您的信息(如果a-->;b那么!b-->;!a)。这意味着你必须放手
编辑2:
实际上,第二个周期会发生——但对于你的“关键数据”,而不是你的“主要膨胀数据”。实际数据将在第一个周期清除
编辑3:
显然,真正的解决方案将使用一个单独的线程从引用队列中移除(不要轮询()、移除()、在专用线程上阻塞)
# 4 楼答案
对于有限数量的资源:子类
SoftReference
。软引用应指向封闭对象。子类中的强引用应该引用资源,因此它总是强可访问的。当通过ReferenceQueue
poll
读取时,可以关闭资源并将其从缓存中删除。需要正确释放缓存(如果SoftReference
本身是垃圾收集的,则不能将其排入ReferenceQueue
的队列)请注意,缓存中只有有限数量的未释放资源——逐出旧条目(实际上,如果适合您的情况,您可以使用if finite cache丢弃软引用)。通常情况下,更重要的是非内存资源,在这种情况下,没有外来引用对象的LRU逐出缓存应该足够了
(我的答案是#1000。伦敦星期五发布。)