Как мы можем выполнить фрагмент кода, используя объект (необходимо его состояние), прежде чем он будет собран, если у нас нет контроля над его источником (мы не можем принудительно реализовать какой-либо интерфейс или, наконец, заблокировать)?
Типы ссылок Java позволяют нам получить доступ к объекту, если кто-то другой делает его полностью доступным + если мы используем очереди ссылок, мы также можем получать уведомления, когда объект собирается, если я не понимаю, что все, что вы можете делать со ссылочными типами, независимо от того, что вы используете в любой момент объект либо полностью достижим, либо его нет, и у вас есть null.
Все, что мне действительно нужно, это способ получить уведомление о том, что конкретный объект собирается быть собранным.
Это излишество, в худшем случае я мог бы обернуть объект в прокси во время выполнения и переопределить завершение прокси и удерживать сильную ссылку на объект, когда прокси станет финализируемым.
когда вызывается финализатор прокси, я буду знать, что у меня есть объект, и никто другой его не использует.
Это не работает. Когда ваш прокси становится финализируемым, это не является доказательством того, что обернутый объект стал недоступен.




Существует причина, по которой 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
Для полноты картины есть хак, позволяющий воскресить собранный объект, который перестанет работать в Java 9.
В документации PhantomReference говорится:
Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued.
Непонятно, почему это было указано и get() метод 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);
}
}
просто еще один пример дерьмового api, созданного разработчиками jdk, вместо того, чтобы дать вам четкий способ сказать: «Привет, jvm, когда этот объект будет очищен, позвольте мне поработать», они дают вам дерьмовый финал ... раздражает
В Java 9 есть API, чтобы сказать именно это, «Выполнить этот Runnable, когда этот объект мертв». Когда вы заменяете значение int в моем первом примере кода ссылкой на Runnable и добавляете поток, опрашивающий очередь и выполняющий их, вы получаете то же самое (и именно так это было реализовано).
@Holger, какая красота кода! Я признаю, что потребовалась бы целая вечность, чтобы понять это в точных деталях, но у меня есть вопрос о Cleaner, если вы не возражаете. Я предполагаю, что когда в документации написано когда объект становится фантомно достижимым, это означает, что теперь он доступен для сборки мусора? Даже если вы теоретически держите сильную ссылку на эту фантомную ссылку? Я понимаю, что get всегда будет возвращать null ...
@Eugene, являющийся фантомная достижимость, подразумевает, что у кого-то есть сильная ссылка на PhantomReference, указывающий на объект (уборщик тоже будет). В противном случае объект - недоступен.
@Eugene, поэтому код этого ответа сохраняет все экземпляры PhantomReference в наборе REGISTERED; они должны оставаться доступными, иначе они будут собраны, как обычные объекты, и ничего не произойдет. Они ставятся в очередь только тогда, когда достижимы (это относится ко всем типам ссылочных объектов).
@Holger не может согласиться, используя этот подход, вам нужно захватить состояние и создать runnable и держать его в ссылке, что, если вы возражаете, продолжая изменять это состояние? возможно, вам нужно прочитать это в последний момент. Если бы эти люди знали, как создавать хорошие API, нам бы даже не пришлось знать, что такое PhantomReference ...
@vach 99,9% разработчиков никогда в жизни не нуждались в PhantomReference. Ваша идея получить доступ к объекту, который должен быть недоступен (что обычно подразумевает наличие недоступный), именно о том, о чем идет речь в «дрянной финализации». Противоречие само по себе. Вы можете обвинять «этих людей», говоря что-то вроде «Если бы эти люди знали, как создавать хорошие API…», но, насколько я могу судить, разработчики C# /. NET тоже потерпели неудачу в этом вопросе, и я не знаю любой лучший пример. Я также сомневаюсь, что именно вы могли бы предложить лучшую альтернативу.
Может, не дуп, а это может иметь полезную информацию? Я думаю, вам нужно изменить саму виртуальную машину, чтобы делать то, что вы хотите.