如何在运行时查找对象是否引用另一个对象

时间:2018-09-21 14:29:33

标签: java garbage-collection weak-references

是否可以在运行时检查某个对象是否直接或间接引用了另一个对象?

(我知道我可以使用VisualVm或类似工具来分析HeapDump,但我想在运行时将其自动化)

我正在使用 WeakHashMap ,它是这样的:

public class MyClass {

    // the Runnable will be eventually removed if the key is collected by the GC
    private static final WeakHashMap<Object, Runnable> map = new WeakHashMap<>();

    public static void main(String[] args) {
        MyClass a = new MyClass(2);
        MyClass b = new MyClass(20);

        a = null;// no more Strong references to a
        b = null;// no more Strong references to b

        System.gc();

        for (Runnable r : map.values()) {
            r.run();
        }
        // will print (20), becouse using format() in the lambda cause a Strong
        // reference to MyClass (b) and thus the WeakHashMap will never remove b
    }

    public MyClass(int i) {

        if (i < 10) {
            map.put(this, () -> {
                System.out.println(i);
            });
        } else {
            map.put(this, () -> {
                // this is subtle, but calling format() create a strong reference 
                // between the Runnable and MyClass
                System.out.println(format(i));
            });
        }
    }

    private String format(Integer i) {
        return "(" + i + ")";
    }

}

在代码中,MyClass的两个实例将自己(作为键)和可运行对象(作为值)添加到WeakHashMap中。 在第一个实例(a)中,Runnable只需调用System.out.println(),并且当实例a不再被引用(a = null)时,该条目将从地图中删除。 在第二个实例(b)中,Runnable也调用format() MyClass的实例函数。这将创建对b的强引用,并将Runnable添加到映射将导致锁定状态,该值是对防止垃圾收集器进行收集的键的间接强引用。

现在我知道要避免这些情况(例如,在lambda中使用Weakreference),但这在实际情况下确实很容易遗漏,并且会导致内存泄漏。

因此,在将对添加到地图之前,我想检查值是否以某种方式引用了键,如果是,则抛出异常。 这将是一个“调试”任务,将在生产中被禁用,所以我不在乎它是否缓慢或被黑客入侵。

---更新---

我正在尝试处理WeakListeners,并避免在未引用的情况下立即收集它们。 所以我将它们注册为notifier.addWeakListener(holder, e -> { ... }); 并且这会将侦听器添加到WeakHashMap中,从而阻止收集侦听器直到持有者处于活动状态。 但是在侦听器中对持有者的任何引用都会创建一个锁:(

有更好的方法吗?

2 个答案:

答案 0 :(得分:3)

通过Reflection API,您可以访问运行时对象的所有字段(及其运行时类型,甚至可能是Class对象)。从理论上讲,您可以遍历实例字段(以及类中的静态字段),字段的字段等的树。

虽然这是可能的,但我怀疑这样做是否可行。您写的是您并不在乎性能,但是对于开发运行而言,它甚至可能太慢。这使我们进入了实现自己的缓存的规则1:不要这样做。

答案 1 :(得分:1)

已经有一个内置的功能,可以自动清除普通实例字段中的关联。即

public class MyClass {

    public static void main(String[] args) {
        MyClass a = new MyClass(2);
        MyClass b = new MyClass(20);

        WeakReference<MyClass> aRef = new WeakReference<>(a), bRef = new WeakReference<>(b);
        a = null;// no more Strong references to a
        b = null;// no more Strong references to b

        System.gc();
        if(aRef.get() == null) System.out.println("a collected");
        if(bRef.get() == null) System.out.println("b collected");
    }

    Runnable r;
    public MyClass(int i) {
        if (i < 10) {
            r = () -> System.out.println(i);
        } else {
            r = () -> {
                // reference from Runnable to MyClass is no problem
                System.out.println(format(i));
            };
        }
    }

    private String format(Integer i) {
        return "(" + i + ")";
    }
}

您可以将这些关联的对象作为 keys 放入弱哈希图中,以允许它们收集垃圾,当然,这仅在特定的MyClass实例时才会发生,仍然对此有很强的引用,会收集垃圾:

public class MyClass {
    public static void main(String[] args) {
        MyClass a = new MyClass(2);
        MyClass b = new MyClass(20);

        for(Runnable r: REGISTERED) r.run();

        System.out.println("cleaning up");

        a = null;// no more Strong references to a
        b = null;// no more Strong references to b

        System.gc();

        // empty on common JRE implementations
        for(Runnable r: REGISTERED) r.run();
    }

    static Set<Runnable> REGISTERED = Collections.newSetFromMap(new WeakHashMap<>());

    Runnable r;
    public MyClass(int i) {
        r = i < 10?
            () -> System.out.println(i):
            () -> {
                // reference from Runnable to MyClass is no problem
                System.out.println(format(i));
            };
        REGISTERED.add(r);
    }

    private String format(Integer i) {
        return "(" + i + ")";
    }
}

但是请注意,在此简单的测试设置中可以正常工作的内容是您不应该依赖的,尤其是您提到弱听者时。

在生产环境中,垃圾收集器在存在内存需求时运行,而内存需求未连接到应用逻辑,即是否应执行实现为侦听器的特定操作或不。一种可能的情况是,总是有足够的内存,因此垃圾收集器永远不会运行,过时的侦听器将永远被执行。

但是您也可能在另一个方向遇到问题。您的问题表明,编写侦听器(示例中的Runnable可能不包含对实例的引用,该实例的寿命应确定侦听器的生存时间({{1 }}实例)。这就提出了一个问题,即这些对象的寿命是如何连接的。为了使这些侦听器保持活动状态,您必须保留对这些关键对象的强引用,这也容易出错。