跟踪 Java 中的引用

Keeping track of references in Java

在Java中,有什么方法可以判断是否正在维护对其他线程(或一般)中对象的引用?

考虑以下 class:

public class ResourcePool<TYPE>
{
    private final Queue<Object> emptyLocks = new LinkedBlockingQueue<>();

    private final Queue<TYPE> available = new LinkedBlockingQueue<>();
    private final Queue<TYPE> claimed = new LinkedBlockingQueue<>();

    public void add(TYPE resource)
    {
        available.add(resource);
    }

    public TYPE claim() throws InterruptedException
    {
        if (available.isEmpty())
        {
            Object lock = new Object();
            synchronized (lock)
            {
                emptyLocks.add(lock);
                lock.wait();
            }
        }

        TYPE resource = available.poll();
        claimed.add(resource);
        return resource;
    }

    public void release(TYPE resource)
    {
        if (!claimed.remove(resource)) return;
        available.add(resource);

        Object lock = emptyLocks.poll();
        if (lock != null) synchronized (lock) {lock.notify();}
    }
}

这里的想法是多个线程可以 claim/release 资源,这样两个线程永远不会在任何给定时刻拥有相同的资源。但是如果一个线程忘记释放()一个资源会发生什么?更糟糕的是,如果线程调用 release() 然后 继续使用资源 ?

怎么办?

使用 Wea​​kReference class,可以判断何时不存在对给定对象的更多强引用。但是,当发生这种情况时,该对象将被垃圾回收并消失。 SoftReference 可能有效,但是在我们将资源放回 "available" 列表之前,我们的资源仍有可能被 GC 处理。

所以问题来了:有没有办法跟踪这些资源是否仍在实际使用?

理想情况下,线程可以 claim() 资源,想用多久就用多久,一旦不再有引用被维护,这些资源就会自动释放。我认为这会非常优雅,并且在其他情况下也很有用。

您正在搜索 finalize() 方法。当对象不再可访问时,您必须重写此方法以进行清理。

请注意 创建 finalize() 方法并不像看起来那么容易(explained in these answers)。

答案很简单:否

GC 在 VM 全局级别工作,并且在当前实现中(至少在 Hotspot 中)它不使用引用计数,更不用说引用跟踪了。这意味着即使是 VM 也不总是知道在任意时间点引用了什么。

GC 通常也是跟踪需要及时释放 事物的错误工具; GC 可能 运行 的频率非常低(在极端情况下,GC 可能会每隔 小时 调整一次 运行 一次)。您可能希望您的资源在拥有块作用域结束后立即可供其他线程使用;与 GC 的松散处理截然不同的要求。

您想要的是立即检测超出每个线程范围的事情。虽然这是一个不错的功能,但问题是它是否值得付出性能影响成本。

您的资源示例和一个线程 "forgetting" 在释放后释放/保留资源是可以按照惯例处理的事情:使用 try-finally 或滥用 try-with-resources 块确保生命周期得到妥善维护。

要捕获资源的意外泄漏,可以滥用finalize;但它再次不可预测 资源完成时(你只能做清理,不应该恢复资源;请参阅关于完成的 JLS)。

编辑:您可以轻松地防御 使用资源 after 释放资源,方法是在资源中查找所有者。然后资源可以运行时间检查调用线程确实是当前所有者。