如果我们无法控制其来源(无法强制实施某些界面或最终阻止),我们如何在收集对象之前使用该对象(其状态是必需的)来执行?
Java引用类型允许我们访问对象,如果其他人使其可以强烈访问+如果我们使用引用队列,我们也可以在收集对象时得到通知,除非我的理解是错误的,所有你可以做的使用引用类型,无论你在任何时候使用什么,对象都是强烈可达的,或者它已经消失,你就是null。
我真正需要的是一种在即将收集特定对象时获得通知的方法。
答案 0 :(得分:2)
Reference API不允许检索收集的对象是有原因的:允许再次访问收集的对象,就像使用finalize()
方法一样,这正是非预期的。
标准方法是创建引用类型的子类以存储与引用对象相关联的信息,例如,在专用参考对象中执行清理操作所需的一切。当然,这些信息不得包含对所指对象的强烈引用。
private static final ReferenceQueue<Integer> QUEUE = new ReferenceQueue<>();
static class IntegerPhantomReference extends PhantomReference<Integer> {
final int value;
public IntegerPhantomReference(Integer ref) {
super(ref, QUEUE);
value = ref.intValue();
}
public String toString() {
return "Integer[value="+value+"]";
}
}
private static final Set<IntegerPhantomReference> REGISTERED = new HashSet<>();
public static void main(String[] args) throws InterruptedException {
List<Integer> stronglyReferenced = new ArrayList<>();
for(int i = 0; i < 10; i++) {
Integer object = new Integer(i);
stronglyReferenced.add(object);
REGISTERED.add(new IntegerPhantomReference(object));
}
gcAndPoll("initial");
stronglyReferenced.removeIf(i -> i%2 == 0);
gcAndPoll("after removing even");
stronglyReferenced.clear();
gcAndPoll("after remove all");
if(REGISTERED.isEmpty()) System.out.println("all objects collected");
}
private static void gcAndPoll(String msg) throws InterruptedException {
System.out.println(msg);
System.gc(); Thread.sleep(100);
for(;;) {
Reference<?> r = QUEUE.poll();
if(r == null) break;
System.out.println("collected "+r);
REGISTERED.remove(r);
}
}
initial
after removing even
collected Integer[value=4]
collected Integer[value=8]
collected Integer[value=6]
collected Integer[value=2]
collected Integer[value=0]
after remove all
collected Integer[value=1]
collected Integer[value=5]
collected Integer[value=3]
collected Integer[value=7]
collected Integer[value=9]
all objects collected
为了完整性,有一个hack允许复活收集的对象,这将停止在Java 9中工作。
PhantomReference
的文档说:
与软引用和弱引用不同,垃圾收集器在排队时不会自动清除幻像引用。
目前尚不清楚为什么指定了这个,并且get()
method of PhantomReference
已被覆盖以始终返回null
,完全不允许从此参考尚未清除的事实中获益。由于此特殊行为的目的尚不清楚,因此已从Java 9中的规范中删除,并且这些引用会像其他引用一样自动清除。
但是对于以前的版本,可以使用带访问覆盖的Reflection来访问引用,以完全实现API不允许的内容。毋庸置疑,这只是出于提供信息的目的而且非常劝阻(如上所述,它停止在Java 9中工作)。
private static final ReferenceQueue<Integer> QUEUE = new ReferenceQueue<>();
private static final Set<PhantomReference<Integer>> REGISTERED = new HashSet<>();
public static void main(String[] args)
throws InterruptedException, IllegalAccessException {
List<Integer> stronglyReferenced = new ArrayList<>();
for(int i = 0; i < 10; i++) {
Integer object = new Integer(i);
stronglyReferenced.add(object);
REGISTERED.add(new PhantomReference<>(object, QUEUE));
}
gcAndPoll("initial");
stronglyReferenced.removeIf(i -> i%2 == 0);
gcAndPoll("after removing even");
stronglyReferenced.clear();
gcAndPoll("after remove all");
if(REGISTERED.isEmpty()) System.out.println("all objects collected");
}
static final Field REFERENT;
static {
try {
REFERENT = Reference.class.getDeclaredField("referent");
REFERENT.setAccessible(true);
} catch (NoSuchFieldException ex) {
throw new ExceptionInInitializerError(ex);
}
}
private static void gcAndPoll(String msg)
throws InterruptedException, IllegalAccessException {
System.out.println(msg);
System.gc();
Thread.sleep(100);
for(;;) {
Reference<?> r = QUEUE.poll();
if(r == null) break;
Object o = REFERENT.get(r);
System.out.println("collected (and now resurrected)"+o);
REGISTERED.remove(r);
}
}