[Java] для чего нужны PhantomReference?

ifani

Помнится, когда-то давно прочитал про них в Thinking in Java, не понял и забыл/забил :)
На днях решил, что пришло время разобраться - прочитал и опять не понял :grin:
Кто-нибудь может дать простой юзкейс, который можно реализовать только с помощью PhantomReference?
PS:
Я, естественно, сначала почитал документацию, потом ещё погуглил, но в всё равно не понял их назначения :(

danilov

Упс. Попутал в phantom

ifani

А зачем удалил пост? :)
Полностью согласен, что с PhantomReference основная проблема, что в отличие Weak и Soft (по которым хотя бы рекомендации есть для неё не понятно, когда будет удалён соответствующий объект.
Если верить документации, то основная фишка Phantom ссылок в том, сама ссылка будет помещена в соответствующую очередь как только gc решит удалить объект, но сам объект на который они ссылаются, не будет удалён пока будет существовать ссылка.
Получается, что это нужно для каких-то случаев с освобождением ресурсов. Но, если это внутренние ресурсы, то для этого есть finalize (а ещё лучше, конечно, сразу и явно освобождать). А если внешние, то на кой чёрт держать объект неудалённым, так как тогда хватит Soft и Weak - они тоже в очередь перемещаются, хоть уже и после фактического удаления объекта.

danilov

Ну, я же не про то отвечал )
Я про phantom модификатор. А именно PhantomReference и правда совершенно непонятная фигня.

ifani

Я про phantom модификатор
Опа, а давно есть такой модификатор? :shocked:

danilov

Ааааа... Меня глючит... Даже не могу вспомнеть, с чем попутал...

katrin2201

Недавно сам натолкнулся. Не факт, что это правда в последней инстанции, но автор довольно убедителен.
По ссылке, кстати, вдобавок разъясняются и soft\weak референсы. Рекомендую прочитать целиком.

Phantom references
A phantom reference is quite different than either SoftReference or WeakReference. Its grip on its object is so tenuous that you can't even retrieve the object — its get method always returns null. The only use for such a reference is keeping track of when it gets enqueued into a ReferenceQueue, as at that point you know the object to which it pointed is dead. How is that different from WeakReference, though?
The difference is in exactly when the enqueuing happens. WeakReferences are enqueued as soon as the object to which they point becomes weakly reachable. This is before finalization or garbage collection has actually happened; in theory the object could even be "resurrected" by an unorthodox finalize method, but the WeakReference would remain dead. PhantomReferences are enqueued only when the object is physically removed from memory, and the get method always returns null specifically to prevent you from being able to "resurrect" an almost-dead object.
What good are PhantomReferences? I'm only aware of two serious cases for them: first, they allow you to determine exactly when an object was removed from memory. They are in fact the only way to determine that. This isn't generally that useful, but might come in handy in certain very specific circumstances like manipulating large images: if you know for sure that an image should be garbage collected, you can wait until it actually is before attempting to load the next image, and therefore make the dreaded OutOfMemoryError less likely.
Second, PhantomReferences avoid a fundamental problem with finalization: finalize methods can "resurrect" objects by creating new strong references to them. So what, you say? Well, the problem is that an object which overrides finalize must now be determined to be garbage in at least two separate garbage collection cycles in order to be collected. When the first cycle determines that it is garbage, it becomes eligible for finalization. Because of the (slim, but unfortunately real) possibility that the object was "resurrected" during finalization, the garbage collector has to run again before the object can actually be removed. And because finalization might not have happened in a timely fashion, an arbitrary number of garbage collection cycles might have happened while the object was waiting for finalization. This can mean serious delays in actually cleaning up garbage objects, and is why you can get OutOfMemoryErrors even when most of the heap is garbage.
With PhantomReference, this situation is impossible — when a PhantomReference is enqueued, there is absolutely no way to get a pointer to the now-dead object (which is good, because it isn't in memory any longer). Because PhantomReference cannot be used to resurrect an object, the object can be instantly cleaned up during the first garbage collection cycle in which it is found to be phantomly reachable. You can then dispose whatever resources you need to at your convenience.
Arguably, the finalize method should never have been provided in the first place. PhantomReferences are definitely safer and more efficient to use, and eliminating finalize would have made parts of the VM considerably simpler. But, they're also more work to implement, so I confess to still using finalize most of the time. The good news is that at least you have a choice.

katrin2201

Если интересно закопаться - то вам сюда =)

ifani

Спасибо, я примерно то же по сути нашёл в других местах, но вопросы всё равно остаются:
his isn't generally that useful, but might come in handy in certain very specific circumstances like manipulating large images: if you know for sure that an image should be garbage collected, you can wait until it actually is before attempting to load the next image, and therefore make the dreaded OutOfMemoryError less likely.

Факт выгрузки изображения можно было бы отследить и через finalize.
With PhantomReference, this situation is impossible — when a PhantomReference is enqueued, there is absolutely no way to get a pointer to the now-dead object (which is good, because it isn't in memory any longer). Because PhantomReference cannot be used to resurrect an object, the object can be instantly cleaned up during the first garbage collection cycle in which it is found to be phantomly reachable. You can then dispose whatever resources you need to at your convenience.

Во-первых, это не решает проблемы с восстановленными объектами, так как если это "чужой" объект с кривым finalize, то проблема никуда не делась, а если это свой объект, то если ты смог написать корректную работу с фантомной ссылкой, то, думаю, и finalize тоже бы нормально написал :) Во-вторых, как раз из-за того, что PhantomReference не даёт ссылку на объект, то придётся где-то ещё хранить информацию, необходимую для корректного освобождения ресурсов (например, какие-нибудь идентификаторы.). И обычно для этого делают своего наследника PhantomReference, который при создании получает все эти данные из объекта и хранит их отдельно. То есть точно так же можно было бы и просто от объекта унаследоваться и реализовать finalize :)
Плюс всё так же непонято, зачем phantomReference внутри себя держит ссылку на финализированный объект - он из-за этого не удаляется из памяти, пока не удалишь ссылку.
Но, в принципе, все подобные статьи сходятся на том, что это рекомендуемая замена finalize. Кстати, мне ещё понравился такой вариант объяснения, что какие-то долгие операции не стоит делать в потоке gc - это всё тормозит, то есть большой и долгий finalize лучше переписать через PhantomReference.
По ссылке, кстати, вдобавок разъясняются и soft\weak референсы.

Да все остальные референсы, в принципе, понятны :) Но, на всякий случай, тоже почитаю

ifani

Кстати, ещё про
in theory the object could even be "resurrected" by an unorthodox finalize method

Тоже про такую возможность читал - это вообще жесть, учитывая, что по документации
The finalize method is never invoked more than once by a Java virtual machine for any given object.

То есть либо восстановленный объект больше никогда не удалится, что вряд ли, либо в следующий раз его удалят без вызовы finalize, чтоб не упирался :)

katrin2201

Во-первых, это не решает проблемы с восстановленными объектами, так как если это "чужой" объект с кривым finalize, то проблема никуда не делась, а если это свой объект, то если ты смог написать корректную работу с фантомной ссылкой, то, думаю, и finalize тоже бы нормально написал
Тут какое дело. ФантомРеференс позволяет сделать код прозрачнее. Под прозрачностью я понимаю степень заметности багов. То есть, если в коде баги скрыты - то прозрачность кода низка. И наоборот. У, кажется, Фаулера, была классная заметка на эту тему, постараюсь найти и сюда запостить линк.
Соотв если у нас легаси код с кучей файналайзов - то повысить его прозрачность и постепенно переработать можно с помощью фантомреференсов.
Я к тому, что не стоит рассматривать только крайние ситуации. Реальный мир как правило сложнее =)
Во-вторых, как раз из-за того, что PhantomReference не даёт ссылку на объект, то придётся где-то ещё хранить информацию, необходимую для корректного освобождения ресурсов (например, какие-нибудь идентификаторы.). И обычно для этого делают своего наследника PhantomReference, который при создании получает все эти данные из объекта и хранит их отдельно. То есть точно так же можно было бы и просто от объекта унаследоваться и реализовать finalize
Не очень понял мысль. Унаследоваться от объекта - это одно (и для файнал объектов невозможно). Унаследоваться от фантомреференса - другое. Кстати, в твоем примере я бы предпочел composition over inheritance. Но это уже так, детали.
Плюс всё так же непонято, зачем phantomReference внутри себя держит ссылку на финализированный объект - он из-за этого не удаляется из памяти, пока не удалишь ссылку.
Зачем хранит ссылку - это вопрос имплементации. Надо ему.
Зачем только фантомно достижимый объект объект не удаляется - как раз чтобы не пропустить момент.
То есть если объект только фантомно достижим - фантомные ссылки попадут в очередь. И тут уже твоя задача эти ссылки обнулить и таким образом окончательно отпустить объект.
Но, в принципе, все подобные статьи сходятся на том, что это рекомендуемая замена finalize. Кстати, мне ещё понравился такой вариант объяснения, что какие-то долгие операции не стоит делать в потоке gc - это всё тормозит, то есть большой и долгий finalize лучше переписать через PhantomReference.
Вот да, отличный use case =)

katrin2201

То есть либо восстановленный объект больше никогда не удалится, что вряд ли, либо в следующий раз его удалят без вызовы finalize, чтоб не упирался
Гм, вот это да, интересно. В JLS про это ни слова. Что-то мне кажется, в джавадоке тупо облажались.

katrin2201

У, кажется, Фаулера, была классная заметка на эту тему, постараюсь найти и сюда запостить линк.
Нашел. Вот. Не Фаулер, и немного не в тему, но почитать все равно интересно.

bansek

по теме не в курсе, но вижу, что по треду предлагается использовать finalize - возможно Phantomreference меньше грузят GC, чем finalize - любой объект имплементирующий finalize сильно добавляет головной боли GC - лишняя очередь, на несколько шагов отложена сборка и т.п. Возможно у PhantomReference таких проблем нет.

katrin2201

Мы это уже обсудили вроде, но сенкс энивей =)
Кстати, заметь больше всего мне понравившуюся мысль, что finalize всегда делается в треде GC, что может серьезно подвесить приложеньку в момент fullgc.

bleyman

ммм.
Когда я читал спецификацию, я прочитал совсем другое, чем
PhantomReferences are enqueued only when the object is physically removed from memory
Во-первых, это было бы дико тупо. То есть совсем, невообразимо тупо. Во-вторых, в спецификации написано:
An object is phantom reachable if it is neither strongly, softly, nor weakly reachable, it has been finalized, and some phantom reference refers to it.
То есть переводя на человеческий (жавовский GC же такой же, как .нетовский был на момент написания?): вначале объект становится unreachable. Потом его собирают. Если в процессе у него обнаруживается файналайзер, его помещают в отдельную очередь (и не освобождают в этом цикле). Далее, в дотнете специальный тред попает его из этой очереди, вызывает файналайзер и всё, стирает нах. В жаве, как я понял, если на этот объект есть фантомреференсы, то после вызова finalize этот объект вместо тотального уничтожения пушится в указанную очередь. Ну, так написано. То есть если я всё понял правильно, то гон про "удаление через одно поколение, а не через два" является полным гоном, и удаляются фантомреференсед обжектс как минимум через три поколения. Но, типа, я ваще не понимаю как то, что я понял, может быть полезно, так что я мог это понять неправильно.
EDIT: ну то есть вообще говоря его действительно могли уже удалить совсем, я был не прав. То есть это получается такая особая очередь, которая может вернуть единственное осмысленное значение: число успешно вызванных файналайзеров (то есть число элементов в ней). Поскольку все референсы в ней возвращают нулл, то осмысленно только их количество, ок. Зачем это нужно — всё равно непонятно.
Ссылки:
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ref/Phanto...
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/ref/packag...

Dasar

а что происходит - если finalize нет, а phantom - есть?

bleyman

Неизвестно, дяденька, я каску на стройке нашёл и не понимаю ваших умных терминов!
То есть не знаю. Ведь несмотря на то, что объект из неё достать нельзя, разные фантомные ссылки на разные объекты всё равно можно различать и говорить что вот у этой уже сфантомился-тко объектец, а у этой живой ищо. Зачем это может быть нужно — неизвестно!

ifani

Унаследоваться от фантомреференса - другое. Кстати, в твоем примере я бы предпочел composition over inheritance. Но это уже так, детали.
Я сам тоже чаще предпочитаю композицию но в данном случае наследование удобно потому, что наследника фантомреференса можно будет поместить в очередь фантомов, и, соответственно, потом его оттуда достать и легко определить, какой же именно объект был финализирован.

ifani

Зачем это нужно — всё равно непонятно
Уже ведь выше обсудили :)
Вынести обработку, связанную с уничтожением объектов, из finalize в отдельное место.

katrin2201

это было бы дико тупо. То есть совсем, невообразимо тупо. Во-вторых, в спецификации написано
В спецификации (читай "в джавадоке") написано правильно. Дядечка-автор статьи, мной приведенной, децл неточно выразился.
(жавовский GC же такой же, как .нетовский был на момент написания?)
Ой, вот уж могу только догадываться...
Далее, в дотнете специальный тред попает его из этой очереди, вызывает файналайзер и всё, стирает нах. В жаве, как я понял, если на этот объект есть фантомреференсы, то после вызова finalize этот объект вместо тотального уничтожения пушится в указанную очередь. Ну, так написано. То есть если я всё понял правильно, то гон про "удаление через одно поколение, а не через два" является полным гоном, и удаляются фантомреференсед обжектс как минимум через три поколения. Но, типа, я ваще не понимаю как то, что я понял, может быть полезно, так что я мог это понять неправильно.
Понял ты все правильно. Попытаюсь объяснить логику, которая за этим стоит, как я ее понимаю.
Главная печаль не в кол-ве поколений (бтв для джавы тут имхо более корректно применять термин "garbage cycle" которое переживет готовящийся к отправке в лучшие миры объект.
Дело в том, что в той реализации ГЦ, которая есть в джаве, определенные ситуации дают сильную нагрузку на этот самый ГЦ, заставляя его тупить.
Например, из не относящегося к топику, джавовский ГЦ не любит, когда внутри старых объектов живут ссылки на новые. Ему становится тяжело избавляться от таких новых объектов, как следствие требуется больше fullgc циклов, как следствие, оверхед от ГЦ существенно возрастает. В частности, именно из-за этой особенности, в джаве традиционно "любят" иммутабл объекты: иммутабл обертки над примитивами, иммутабл строчки, итд.
Из относящегося я для себя смог выделить следующее:
1. ГЦ очень не любит, когда в файналайзе объект снова "оживает".
2. Файналайз выполняется в треде ГЦ, что существенно тормозит ГЦ. Это сродни тому, как если бы мы в обработку прерывания от мышки впихнули расчет числа пи.
Соответственно, если использовать _только_ ФантомРеференсы, то мы принципиально не можем написать код, который наступит на эти две вышеобозначенные грабли. Этакая защита от дурака.
Мешать файналайзы с фантомреференсами, имеет смысл, только пожалуй, в переходный период от одного к другому. Других разумных объяснений для такого смешивания я не могу придумать.
EDIT: ну то есть вообще говоря его действительно могли уже удалить совсем, я был не прав. То есть это получается такая особая очередь, которая может вернуть единственное осмысленное значение: число успешно вызванных файналайзеров (то есть число элементов в ней). Поскольку все референсы в ней возвращают нулл, то осмысленно только их количество, ок. Зачем это нужно — всё равно непонятно.
Не, вот это вот что-то неправильно имхо, если я правильно понял то, что ты пытался сказать.
Пока ты у фантомреференса руками не вызовешь clear (или она сама не станет анричебл) - память соответсвующим фантом-ричебл объектом будет занята. Почему было сделано именно так (пред-нотификация, а не пост-) я не знаю. Вполне возможно опять же, что для той же совместимости с устаревшим файналайз.

katrin2201

Файналайза не может не быть. Он есть в классе Object (с пустой реализацией от которого в джаве все классы унаследованы.

katrin2201

Я сам тоже чаще предпочитаю композицию но в данном случае наследование удобно потому, что наследника фантомреференса можно будет поместить в очередь фантомов, и, соответственно, потом его оттуда достать и легко определить, какой же именно объект был финализирован.
Убедил, согласен =)

bansek

дефолтный файналайз афаик игнорится, т.е. объект пролетает мимо той файналйз очереди, о которой говорил фиджей (похоже мы с ним читали одну и ту же книжку по тому, как устроен дотнетовский гц =) )

katrin2201

Охотно верю. Просто на поведение программы это никак по-моему не влияет.
Кроме разве того, что собрать объект можно будет прям сейчас, что гц собственно и сделает. Впрочем, эт овроде уже тоже здесь отмечалось...

bleyman

Пока ты у фантомреференса руками не вызовешь clear (или она сама не станет анричебл) - память соответсвующим фантом-ричебл объектом будет занята.

Wait a fucking second. Там же английским языком написано, что getobject всегда возвращает нулл. То есть если ты пытаешься сказать, что эта шняжка игнорирует файналайзеры и позволяет их вызывать ручками из отдельного треда (что, кстати, дотнетовский GC делает сам то я как бы не очень понимаю, как. Типа, к объекту ты доступиться не можешь, но когда ты его, то есть фантомреференс на него, попнешь из очереди, у него вызовется файналайзер уже в том треде, в котором ты его попнул? Или не вызовется, то есть это такой дико кривущий аналог GC.SuppressFinalize(obj)? Как-то это чересчур тупо, даже для жавы =)

katrin2201

Нененененене, Дэвид Блейн =)
Смотри. ФантомРеференсы на вызов файналайза никак не влияют. Более того, чтобы объект стал фантом-ричебл, он должен быть финализирован. Это в одной из твоих же ссылок написано.
Когда я говорю, что ФантомРеференсы позволяют финализировать объекты в отличном от ГЦ треде, то я подразумеваю, что для этого будет создана своя РеференсКью, которую ты сам ручками в отдельном нужном тебе треде будешь мониторить, по появляющимся там референсам как-то понимать (например, унаследовавшись от них, как предлагал что же именно тебе нужно там доосвободить, и ручками делать это. На метод finalize и его вызов эта схема вообще никак не завязана. Дефолтный он у нас в этом случае.
Оставить комментарий
Имя или ник:
Комментарий: