Почему мои объекты не умирают?
Я пытаюсь реализовать механизм, который удаляет кэшированные файлы, когда объекты, которые их содержат, умирают, и решил использовать PhantomReference
s, чтобы получить уведомление о сборке мусора объекта. Проблема в том, что я продолжаю испытывать странное поведение 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 Сохранить изображение в файл: <>\AppData\Local\Temp\tmp7..0.PNG
2013-08-05 22: 35: 03,379 DEBUG Удаление неиспользуемого файла: <>\AppData\Local\Temp\tmp7..0.PNG создан в понедельник, 05 августа, 22:35:02 IDT 2013
1 ответ
Ответ в том, что в вашем примере 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
Это означает, что все, что вы хотели сделать со справочной очередью, будет сделано, когда элемент еще жив.