是否可以在运行时检查某个对象是否直接或间接引用了另一个对象?
(我知道我可以使用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中,从而阻止收集侦听器直到持有者处于活动状态。
但是在侦听器中对持有者的任何引用都会创建一个锁:(
有更好的方法吗?
答案 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 }}实例)。这就提出了一个问题,即这些对象的寿命是如何连接的。为了使这些侦听器保持活动状态,您必须保留对这些关键对象的强引用,这也容易出错。