使用Java的ReferenceQueue

29

创建为实例变量时,SoftReferenceWeakReference是否真的有帮助?在方法范围内使用它们是否有任何好处?

另一个重要的部分是ReferenceQueue。除了能够跟踪确定为垃圾的引用外,Reference.enqueue()能否用于强制注册对象以进行垃圾收集?

例如,是否值得创建一个方法,将某些占用大量内存资源(由强引用持有)的对象并创建引用以将它们加入队列中?

Object bigObject;
public void dispose() {
    ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
    WeakReference<Object> ref = new WeakReference<Object>(bigObject, queue);
    bigObject = null;
    ref.enqueue();
}

(假设这里的Object表示使用了大量内存的对象类型,例如BufferedImage或其他类似的东西)

这是否有任何实际影响呢?还是这只是代码浪费?


略有关联:https://codereview.stackexchange.com/q/28534/4394 - Pacerier
4个回答

39

在引用队列中经常使用的一种习惯用法是,例如,通过子类化WeakReference来附加需要清除的信息,然后轮询ReferenceQueue以获取清理任务。

ReferenceQueue<Foo> fooQueue = new ReferenceQueue<Foo>();

class ReferenceWithCleanup extends WeakReference<Foo> {
  Bar bar;
  ReferenceWithCleanup(Foo foo, Bar bar) {
    super(foo, fooQueue);
    this.bar = bar;
  }
  public void cleanUp() {
    bar.cleanUp();
  }
}

public Thread cleanupThread = new Thread() {
  public void run() {
    while(true) {
      ReferenceWithCleanup ref = (ReferenceWithCleanup)fooQueue.remove();
      ref.cleanUp();
    }
  }
}

public void doStuff() {
  cleanupThread.start();
  Foo foo = new Foo();
  Bar bar = new Bar();
  ReferenceWithCleanup ref = new ReferenceWithCleanup(foo, bar);
  ... // From now on, once you release all non-weak references to foo,
      // then at some indeterminate point in the future, bar.cleanUp() will
      // be run. You can force it by calling ref.enqueue().
}
例如,当选择weakKeys时,Guava的CacheBuilder实现的内部使用此方法。

1
它还说GC不使用它。那么为什么会存在呢? - bgroenks
1
如果您想显式调用它,这种情况下也可以。很多这样的东西有着非常狭窄的使用场景,但是存在的原因是除非显式提供,否则将无法获得。 - Louis Wasserman
1
你什么时候想要显式地调用它? - bgroenks
1
我从未听说过一个令人信服的使用案例,但如果您想要明确地清理某些东西,无论引用是否已经被垃圾回收,也许可以考虑这种方法。 - Louis Wasserman
1
如果您的路径包括调用清理方法的设施,但使用了WeakReferences和ReferenceQueue来防止松散代码导致资源耗尽,那么您将显式地将对象加入队列。像第三方连接池类之类的东西会这样做。 - nsayer
显示剩余5条评论

8
如果一个对象只有WeakReference(或者根本没有任何引用!),那么当Java需要在内存中腾出更多空间时,它可以被垃圾回收。因此,当您想让一个对象保留在内存中,但不需要太强烈地保留它(例如,如果Java需要对其进行垃圾回收,那么没问题,您可以找回它,并且同时Java的性能更好),则使用WeakReference
WeakReference入队允许您迭代ReferenceQueue并确定哪些引用已经被垃圾回收,哪些还没有。仅在需要知道这一点时才执行此操作。
阅读更多:http://weblogs.java.net/blog/2006/05/04/understanding-weak-references

我不认为这是保证完成的。enqueue() 实际上是做什么的? - bgroenks
1
@bgroenks Enqueueing 指将项放置在关联队列的尾部。从技术上讲,您无法“强制”GC执行任何操作。 GC甚至从未调用enqueue()方法,而是直接排队引用。确保对象符合垃圾回收的唯一方法是销毁与该对象相关的所有实时引用。 - arkon
2
@Patashu,如果我可以使用 if (weekReference.get() == null) 来判断,为什么我需要一个 ReferenceQueue 来“确定哪些引用已经被 GC 回收了”? - Gavriel
3
因为像while (weakReference.get() != null)这样的忙等待会消耗太多的CPU资源。 ReferenceQueue 让你可以休眠直到有一个对象可以被终结。 - Tavian Barnes
@Patashu,既然已经有WeakHashMap了,那么说ReferenceQueue是无意义的,这种说法是否正确? - Pacerier
显示剩余3条评论

5

常见做法之一是创建软引用的映射表。

Map<String, SoftReference<BigThing>> cache = new HashMap<>();
Set<String> thingsIAmCurrentlyGetting = new HashSet<String>();
Object mutex = new Object();

BigThing getThing(String key) {
  synchronized(mutex) {
    while(thingsIAmCurrentlyGetting.contains(key)) {
      mutex.wait();
    }
    SoftReference<BigThing> ref = cache.get(key);
    BigThing bigThing = ref == null ? null : ref.get();
    if(bigThing != null) return bigThing;
    thingsIAmCurrentlyGetting.add(key);
  }

  BigThing bigThing = getBigThing(key); // this may take a while to run.

  synchronized(mutex) {
    cache.put(key, bigThing);
    thingsIAmCurrentlyGetting.remove(key);
    mutex.notifyAll();
  }

  return bigThing;
}

我这里展示的是我的老代码 - 新的Java包可能有更简洁的方法来实现这个功能。


哦,等一下 - 这根本不是你的问题!这完全无关紧要! - PaulMurrayCbr
说实话,这实际上是一个非常好的现实世界的例子,展示了如何正确使用软/弱引用来正确地惰性初始化对象(无竞争条件或锁),并通知其他等待线程...他忘了说,创建和排队引用根本没有帮助,只是浪费代码。你需要将所有非垃圾回收的引用指向一个大对象,并将它们置空(以免阻止GC...) - Ajax

1

不确定这里的问题是什么,但:

1)软引用尝试保留引用,直到JVM真正需要内存。非常适合缓存,尤其是LRU缓存。在Guava中有很多示例。

2)弱引用根本不会阻止gc释放对象。如果您想知道该对象是否仍在某个地方使用,则可以使用它们。例如,它们用于存储有关线程和类的信息,以便当不再使用线程或类时,我们可以丢弃与之相关的元信息。

3)虚引用就像弱引用,但不允许您引用实际对象。通过这种方式,您可以确保传递幻像不能恢复实际对象(这是弱引用的风险)。此外,虚引用阻止对象被收集,直到您清除引用。

ReferenceQueue:您不必将内容排队。 gc会为您完成。它们使您无需逐个检查引用即可知道何时释放某些引用。


“同时,幽灵引用会阻止对象被回收,直到您清除该引用。” 这没有意义。 - Aleksandr Dubinsky
在Java 8及以下版本中,虚引用在入队到ReferenceQueue时不会自动清除,因此它们所引用的对象将保持虚可达状态,直到您将虚引用从ReferenceQueue中取出才能回收。这在Java 9中得到了改变,使得虚引用在入队时被清除。 - user102008

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接