Вы когда-нибудь использовали PhantomReference в каком-либо проекте?
Единственное, что я знаю о PhantomReference
является,
- Если вы используете его
get()
метод, он всегда вернетnull
а не объект. Какая от этого польза? - Используя
PhantomReference
вы убедитесь, что объект не может быть воскрешен изfinalize
метод.
Но какая польза от этого понятия / класса?
Вы когда-нибудь использовали это в своем проекте или у вас есть пример, где мы должны это использовать?
11 ответов
Я использовал PhantomReference
s в упрощенном, очень специализированном виде профилировщика памяти для мониторинга создания и уничтожения объектов. Мне нужно, чтобы они отслеживали разрушения. Но подход устарел. (Он был написан в 2004 году для J2SE 1.4.) Профессиональные инструменты профилирования намного мощнее и надежнее, и для этого также можно использовать более новые функции Java 5, такие как JMX или агенты и JVMTI.
PhantomReference
s (всегда используется вместе с эталонной очередью) превосходят finalize
что имеет некоторые проблемы и поэтому следует избегать. Главным образом делая объекты снова достижимыми. Этого можно избежать с помощью идиомы хранителя финализатора (-> подробнее в "Эффективной Java"). Таким образом, они также являются новой доработкой.
Более того, PhantomReference
s
позволяют точно определить, когда объект был удален из памяти. На самом деле это единственный способ определить это. Как правило, это не так полезно, но может пригодиться в определенных очень специфических обстоятельствах, таких как манипулирование большими изображениями: если вы точно знаете, что изображение должно собираться мусором, вы можете подождать, пока оно действительно будет, прежде чем пытаться загрузить следующее изображение и, следовательно, сделать страшный OutOfMemoryError менее вероятным. (Цитируется из enicholas.)
И, как писал в первую очередь PSD, у Роди Грина есть хорошее резюме.
Общее пояснение таблицы, из глоссария Java.
Что, конечно, совпадает с документацией PhantomReference:
Фантомные эталонные объекты, которые ставятся в очередь после того, как сборщик определит, что их референты могут быть возвращены в противном случае. Фантомные ссылки чаще всего используются для более гибкого планирования предварительных действий по очистке, чем это возможно с механизмом финализации Java.
И последнее, но не менее важное, все кровавые подробности (это хорошее чтение): Справочные объекты Java (или Как я научился прекращать беспокоиться и люблю OutOfMemoryError).
Удачного кодирования. (Но чтобы ответить на вопрос, я когда-либо использовал WeakReferences.)
Отличное объяснение использования Phantom Reference:
Фантомные ссылки - это безопасный способ узнать, что объект был удален из памяти. Например, рассмотрим приложение, которое имеет дело с большими изображениями. Предположим, что мы хотим загрузить большое изображение в память, когда большое изображение уже находится в памяти и готово для сбора мусора. В таком случае мы хотим подождать, пока старое изображение будет собрано, прежде чем загружать новое. Здесь, фантомная ссылка является гибкой и безопасной возможностью выбора. Ссылка на старое изображение будет помещена в очередь в ReferenceQueue после завершения работы со старым объектом изображения. Получив эту ссылку, мы можем загрузить новое изображение в память.
Я нашел практический и полезный случай использования PhantomReference
который org.apache.commons.io.FileCleaningTracker
в общем проекте. FileCleaningTracker
удалит физический файл, когда его маркерный объект будет собран мусором.
Что-то, чтобы принять к сведению Tracker
класс, который расширяется PhantomReference
учебный класс.
ЭТО ДОЛЖНО БЫТЬ НАБЛЮДЕННЫМ С JAVA 9!
использование java.util.Cleaner
вместо! (Или же sun.misc.Cleaner
на старшей JRE)
Исходное сообщение:
Я обнаружил, что использование PhantomReferences имеет почти столько же ловушек, что и методы финализатора (но меньше проблем, если вы все сделаете правильно). Я написал небольшое решение (очень маленькую платформу для использования PhantomReferences) для Java 8. Оно позволяет использовать лямбда-выражения в качестве обратных вызовов, запускаемых после удаления объекта. Вы можете зарегистрировать обратные вызовы для внутренних ресурсов, которые должны быть закрыты. С этим я нашел решение, которое работает для меня, поскольку оно делает его гораздо более практичным.
https://github.com/claudemartin/java-cleanup
Вот небольшой пример, чтобы показать, как зарегистрирован обратный вызов:
class Foo implements Cleanup {
//...
public Foo() {
//...
this.registerCleanup((value) -> {
try {
// 'value' is 'this.resource'
value.close();
} catch (Exception e) {
logger.warning("closing resource failed", e);
}
}, this.resource);
}
И затем есть еще более простой метод для автоматического закрытия, примерно такой же, как указано выше:
this.registerAutoClose(this.resource);
Чтобы ответить на ваши вопросы:
[тогда какая польза от этого]
Вы не можете очистить то, что не существует. Но у него могли быть ресурсы, которые все еще существуют, и их необходимо очистить, чтобы их можно было удалить.
Но какая польза от этого понятия / класса?
Это не обязательно делать что-либо с каким-либо эффектом, кроме отладки / регистрации. Или, может быть, для статистики. Я вижу это больше как сервис уведомлений от GC. Вы также можете использовать его для удаления агрегированных данных, которые становятся неактуальными после удаления объекта (но, вероятно, для этого есть более подходящие решения). В примерах часто упоминается, что соединения с базой данных должны быть закрыты, но я не понимаю, насколько это хорошая идея, поскольку вы не могли работать с транзакциями. Прикладная структура предоставит намного лучшее решение для этого.
Вы когда-нибудь использовали это в своем проекте или у вас есть пример, где мы должны это использовать? Или эта концепция создана только для интервью;)
Я использую это в основном только для регистрации. Таким образом, я могу отследить удаленные элементы и посмотреть, как GC работает и может быть настроен. Я бы не стал запускать какой-либо критический код таким образом Если что-то должно быть закрыто, то это должно быть сделано в инструкции try-with-resource. И я использую его в модульных тестах, чтобы убедиться, что у меня нет утечек памяти. Так же, как это делает Джонтей. Но мое решение немного более общее.
Я использовал PhantomReference в модульном тесте, чтобы убедиться, что тестируемый код не хранит ненужные ссылки на какой-либо объект. ( Оригинальный код)
import static com.google.common.base.Preconditions.checkNotNull;
import static org.fest.assertions.Assertions.assertThat;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import com.google.common.testing.GcFinalization;
/**
* Helps to test for memory leaks
*/
public final class MemoryTester
{
private MemoryTester()
{
}
/**
* A simple {@link PhantomReference} that can be used to assert that all references to it is
* gone.
*/
public static final class FinalizationAwareObject extends PhantomReference<Object>
{
private final WeakReference<Object> weakReference;
private FinalizationAwareObject(Object referent, ReferenceQueue<Object> referenceQueue)
{
super(checkNotNull(referent), referenceQueue);
weakReference = new WeakReference<Object>(referent, referenceQueue);
}
/**
* Runs a full {@link System#gc() GC} and asserts that the reference has been released
* afterwards
*/
public void assertThatNoMoreReferencesToReferentIsKept()
{
String leakedObjectDescription = String.valueOf(weakReference.get());
GcFinalization.awaitFullGc();
assertThat(isEnqueued()).as("Object: " + leakedObjectDescription + " was leaked").isTrue();
}
}
/**
* Creates a {@link FinalizationAwareObject} that will know if {@code referenceToKeepTrackOff}
* has been garbage collected. Call
* {@link FinalizationAwareObject#assertThatNoMoreReferencesToReferentIsKept()} when you expect
* all references to {@code referenceToKeepTrackOff} be gone.
*/
public static FinalizationAwareObject createFinalizationAwareObject(Object referenceToKeepTrackOff)
{
return new FinalizationAwareObject(referenceToKeepTrackOff, new ReferenceQueue<Object>());
}
}
И тест:
@Test
public void testThatHoldingOnToAnObjectIsTreatedAsALeak() throws Exception
{
Object holdMeTight = new String("Hold-me-tight");
FinalizationAwareObject finalizationAwareObject = MemoryTester.createFinalizationAwareObject(holdMeTight);
try
{
finalizationAwareObject.assertThatNoMoreReferencesToReferentIsKept();
fail("holdMeTight was held but memory leak tester did not discover it");
}
catch(AssertionError expected)
{
assertThat(expected).hasMessage("[Object: Hold-me-tight was leaked] expected:<[tru]e> but was:<[fals]e>");
}
}
Я использовал его на заре Android. Обратно к ним BitmapDrawable имел базовый Bitmap, который использовал память, которая не была выделена в пространстве Java Heap, что означало, что вы использовали память, но JVM не чувствовала давления на память. Симптомом было то, что приложение вылетало из-за нехватки памяти, но вы никогда не обнаружили, что был вызван ваш финализатор (или что вообще не было очистки сборки мусора).
Итак, я создал подкласс PhantomReference, который имел жесткую ссылку на Bitmap (переменную класса). Сама PhantomReference указала на BitmapDrawable.
Когда фантомная ссылка выходит из очереди, указывая, что BitmapDrawable больше не доступен, я вызываю метод release для Bitmap, который освобождает память, находящуюся за пределами кучи vm.
Он отлично работал, он более или менее полностью устранил сбои из-за нехватки памяти, которые возникали из-за этой странной модели памяти на ранних версиях Android.
Позже Android изменил модель памяти, чтобы загружать растровые изображения в кучу JVM, кстати, потому что вы действительно хотите, чтобы ваша виртуальная машина чувствовала давление памяти, если приложению не хватает памяти. Это определенно была ситуация «черного алмаза» - освободить память, подобную этой, и я сомневаюсь, что большинство разработчиков приложений понимали, что сборщик мусора им не поможет, потому что память находилась вне поля зрения сборщика мусора. Вероятно, они просто сочли это ошибкой Android.
Это распространено в использовании WeakReference
где PhantomReference
более уместно. Это позволяет избежать определенных проблем, связанных с возможностью воскрешения объектов после WeakReference
очищается / ставится в очередь сборщиком мусора. Обычно разница не имеет значения, потому что люди не играют в глупых пижонов.
С помощью PhantomReference
имеет тенденцию быть немного более навязчивым, потому что вы не можете делать вид, что get
метод работает. Вы не можете, например, написать Phantom[Identity]HashMap
,
Я нашел еще одно практическое применение PhantomReferences
в классе LeakDetector Jetty.
Jetty использует LeakDetector
класс, чтобы определить, получает ли клиентский код ресурс, но никогда не освобождает его, и LeakDetector
класс использует PhantomReferences
для этого.
если вы используете метод get(), он всегда будет возвращать ноль, а не объект. [тогда какая польза от этого]
Полезные методы для вызова (а не get()
) было бы isEnqueued()
или же referenceQueue.remove()
, Вы бы вызвали эти методы, чтобы выполнить какое-то действие, которое должно произойти в последнем раунде сборки мусора объекта.
Первый раз, когда объект имеет свой finalize()
Вызванный метод, так что вы можете поставить туда закрывающие хуки. Однако, как утверждали другие, возможно, существуют более надежные способы очистки или какие-либо действия, которые необходимо выполнить до и после сборки мусора или, в более общем плане, по окончании срока службы объекта.
Вот общий пример его использования : в данном случае для некоторого кода Swing в векторном редакторе, где легко создать десятки тысяч
AffineTransform
экземпляры внутри цикла рисования, когда они легко перерабатываются и повторно используются, и это оказалось значительным узким местом при профилировании. Я использовал тот же шаблон для повторного использования
CharBuffer
экземпляров при обработке файлов журнала построчно. По сути, шаблон таков: у вас есть некоторая структура данных, создание которой дорого, и вы можете полностью сбрасывать состояние, вместо того, чтобы каждый раз создавать новую. Итак, вы создаете
PhantomReference
подкласс, который строго ссылается на объект, который вы хотите переработать и повторно использовать, чей референт - это то, что может ссылаться на объект; чтобы отследить, когда безопасно утилизировать объект, вы либо
- Верните фасад для объекта, который реализует тот же интерфейс или что-то достаточно близкое (например, реализацию CharSequence, которая обертывает CharBuffer), и используйте это как референт вашего PhantomReference или
- Вызывающие абоненты передают вам ссылку на себя, чтобы вы могли повторно использовать объект, когда вызывающий абонент выходит за пределы области видимости.
Другими словами, здесь используется шаблон, в котором вы просите очередь сообщить вам, когда каждый объект, который мог знать о какой-либо кэшированной вещи, исчез, поэтому вы можете сделать его доступным для повторного использования другому вызывающему.