我正在尝试实现一种机制,当持有它们的对象死亡时删除缓存文件,并决定使用PhantomReference
来获取有关对象的垃圾收集的通知。问题是我一直在经历ReferenceQueue
的奇怪行为。当我在代码中更改某些内容时,它突然不再获取对象。所以我试着用这个例子进行测试,并遇到了同样的问题:
public class DeathNotificationObject {
private static ReferenceQueue<DeathNotificationObject>
refQueue = new ReferenceQueue<DeathNotificationObject>();
static {
Thread deathThread = new Thread("Death notification") {
@Override
public void run() {
try {
while (true) {
refQueue.remove();
System.out.println("I'm dying!");
}
} catch (Throwable t) {
t.printStackTrace();
}
}
};
deathThread.setDaemon(true);
deathThread.start();
}
public DeathNotificationObject() {
System.out.println("I'm born.");
new PhantomReference<DeathNotificationObject>(this, refQueue);
}
public static void main(String[] args) {
for (int i = 0 ; i < 10 ; i++) {
new DeathNotificationObject();
}
try {
System.gc();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果为:
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
I'm born.
毋庸置疑,更改sleep
时间,多次调用gc
等都不起作用。
更新
正如所建议的那样,我打电话给Reference.enqueue()
我的参考文献,它解决了这个问题。
奇怪的是,我有一些完美的代码(只是测试过它),尽管它从不调用enqueue
。是否有可能将Reference
放入Map
以某种方式神奇地将参考文凭列入?
public class ElementCachedImage {
private static Map<PhantomReference<ElementCachedImage>, File>
refMap = new HashMap<PhantomReference<ElementCachedImage>, File>();
private static ReferenceQueue<ElementCachedImage>
refQue = new ReferenceQueue<ElementCachedImage>();
static {
Thread cleanUpThread = new Thread("Image Temporary Files cleanup") {
@Override
public void run() {
try {
while (true) {
Reference<? extends ElementCachedImage> phanRef =
refQue.remove();
File f = refMap.remove(phanRef);
Calendar c = Calendar.getInstance();
c.setTimeInMillis(f.lastModified());
_log.debug("Deleting unused file: " + f + " created at " + c.getTime());
f.delete();
}
} catch (Throwable t) {
_log.error(t);
}
}
};
cleanUpThread.setDaemon(true);
cleanUpThread.start();
}
ImageWrapper img = null;
private static Logger _log = Logger.getLogger(ElementCachedImage.class);
public boolean copyToFile(File dest) {
try {
FileUtils.copyFile(img.getFile(), dest);
} catch (IOException e) {
_log.error(e);
return false;
}
return true;
}
public ElementCachedImage(BufferedImage bi) {
if (bi == null) throw new NullPointerException();
img = new ImageWrapper(bi);
PhantomReference<ElementCachedImage> pref =
new PhantomReference<ElementCachedImage>(this, refQue);
refMap.put(pref, img.getFile());
new Thread("Save image to file") {
@Override
public void run() {
synchronized(ElementCachedImage.this) {
if (img != null) {
img.saveToFile();
img.getFile().deleteOnExit();
}
}
}
}.start();
}
}
一些过滤后的输出:
2013-08-05 22:35:01,932 DEBUG将图像保存到文件:&lt;&gt; \ AppData \ Local \ Temp \ tmp7..0.PNG
2013-08-05 22:35:03,379 DEBUG删除未使用的文件:&lt;&gt; \ AppData \ Local \ Temp \ tmp7..0.PNG创建于2013年8月5日星期一22:35:02 IDT 2013
答案 0 :(得分:6)
答案是,在您的示例中,PhantomReference
本身无法访问,因此在引用对象本身被垃圾收集之前 之前收集垃圾。因此,当对象被GCed时,不再有Reference
并且GC不知道它应该在某处排队。
这当然是某种头对头的比赛: - )
这也解释了(不深入研究新代码)为什么将引用放入一些可访问的集合中会使示例工作。
仅供参考(双关语)这里是你的第一个例子的修改版本(在我的机器上:-)我刚添加了一个包含所有引用的集合。
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashSet;
import java.util.Set;
public class DeathNotificationObject {
private static ReferenceQueue<DeathNotificationObject> refQueue = new ReferenceQueue<DeathNotificationObject>();
private static Set<Reference<DeathNotificationObject>> refs = new HashSet<>();
static {
Thread deathThread = new Thread("Death notification") {
@Override
public void run() {
try {
while (true) {
Reference<? extends DeathNotificationObject> ref = refQueue.remove();
refs.remove(ref);
System.out.println("I'm dying!");
}
} catch (Throwable t) {
t.printStackTrace();
}
}
};
deathThread.setDaemon(true);
deathThread.start();
}
public DeathNotificationObject() {
System.out.println("I'm born.");
PhantomReference<DeathNotificationObject> ref = new PhantomReference<DeathNotificationObject>(this, refQueue);
refs.add(ref);
}
public static void main(String[] args) {
for (int i = 0 ; i < 10 ; i++) {
new DeathNotificationObject();
}
try {
System.gc();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
<强>更新强>
在您的示例中可以手动调用enqueue
,但不能在实际代码中调用。它给出了错误的结果。让我通过在构造函数中调用enqueue
并使用另一个main
:
public DeathNotificationObject() {
System.out.println("I'm born.");
PhantomReference<DeathNotificationObject> ref = new PhantomReference<DeathNotificationObject>(this, refQueue);
ref.enqueue();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0 ; i < 5 ; i++) {
DeathNotificationObject item = new DeathNotificationObject();
System.out.println("working with item "+item);
Thread.sleep(1000);
System.out.println("stopped working with item "+item);
// simulate release item
item = null;
}
try {
System.gc();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出将如下:
I'm born.
I'm dying!
working with item DeathNotificationObject@6908b095
stopped working with item DeathNotificationObject@6908b095
这意味着当项目仍处于活动状态时,无论您想对引用队列做什么,都将完成。