Можно ли отследить объект от его финализатора, чтобы обнаружить случайное воскресение объекта другим финализатором объекта? - PullRequest
1 голос
/ 26 марта 2019

Одной из многих проблем с методами finalize в Java является проблема «воскрешения объекта» (объясненная в этом вопросе ): если объект завершен, и он сохраняет копию this где-то глобально достижимо, ссылка на объект «ускользает», и вы в конечном итоге получаете завершенный, но живой объект (который не будет снова завершен, а в противном случае возникнет проблема).

Чтобы избежать создания воскресших объектов, обычный совет (как, например, в этот ответ ) состоит в том, чтобы создать новый экземпляр объекта, а не сохранить сам объект; Обычно это достигается путем копирования всех полей объекта в новый объект. В большинстве случаев это позволяет достичь возможности освобождения исходного объекта, а не его воскрешения.

Однако Java-сборщик мусора поддерживает сборку мусора ссылочных циклов; это означает, что объект может быть завершен, в то время как (прямо или косвенно) содержит ссылку на себя, и два объекта могут быть завершены, в то время как (прямо или косвенно) содержат ссылки друг на друга. В этом случае совет «скопировать все поля в новый объект» фактически не решает проблему; хотя мы отбрасываем ссылку this после завершения работы финализатора, частично завершенный объект будет воскрешен через ссылку из поля. Таким образом, мы все равно получаем воскрешение объекта.

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

Однако, если два объекта содержат ссылки друг на друга (и, таким образом, оба освобождаются одновременно), и мы создаем новый экземпляр каждого из них, то каждый из новых объектов будет содержать ссылку на старый Завершенный объект (а не новый объект, созданный в качестве замены). Это, очевидно, нежелательное состояние дел, поэтому я хотел бы попытаться использовать то же решение, что и в случае с одним объектом: рекурсивное сканирование полей (живых, вновь построенных) объектов в поисках завершенных объектов. и заменяя их соответствующими объектами замены.

Проблема в том, как я могу распознать завершенный / воскрешенный объект, когда я делаю это? Очевидный способ сделать это состоит в том, чтобы как-то записать идентичность завершенного объекта в финализаторе, а затем сравнить все объекты, которые мы находим во время рекурсивного сканирования, со списком завершенных объектов. Проблема в том, что не существует правильного способа записи идентификатора рассматриваемого объекта:

  • Регулярная (сильная) ссылка будет поддерживать объект живым, эффективно воскрешая его, и не даст никакого метода, с помощью которого можно будет определить, что объект на самом деле не ссылается. Это решило бы проблему идентификации воскресших объектов, но столкнулось с собственной проблемой: хотя воскрешенные объекты никогда не использовались бы, за исключением их идентичности, не было бы никаких средств для их освобождения (например, вы не можете используйте PhantomReference, чтобы обнаружить, что объект теперь действительно мертв, как вы обычно делаете в Java, потому что объект теперь сильно доступен и, таким образом, ссылка на фантом никогда не очищается). Таким образом, это фактически означает, что рассматриваемые объекты остаются выделенными навсегда, вызывая утечку памяти.
  • Использование слабой ссылки было моей первой идеей, но у меня была проблема в том, что в то время, когда мы создаем объект WeakReference, указанный объект на самом деле не является сильно, мягко или слабо достижимым.Таким образом, как только мы сохраняем WeakReference в любом месте, которое является сильно достижимым (чтобы предотвратить освобождение самого WeakReference), цель WeakReference становится слабо достижимой, и ссылка автоматически очищается.Поэтому мы не можем хранить какую-либо информацию таким образом.
  • При использовании фантомной ссылки возникает проблема, заключающаяся в том, что невозможно сравнить фантомную ссылку с объектом, чтобы определить, ссылается ли эта ссылка на этот объект.(Возможно, должно быть - в отличие от get(), который может воскресить объект, в этой операции никогда не будет никакой опасности, потому что у нас все равно есть ссылка на объект - но ее нет в Java API. Аналогично, .equals() для PhantomReference объектов это ==, а не равенство значений, поэтому вы не можете использовать его, чтобы определить, ссылаются ли две фантомные ссылки на одно и то же.)
  • Использование System.identityHashCode() для записи числа, соответствующегоидентичность объекта почти работает - освобождение объекта не изменит записанного номера, число не помешает освобождению объекта, а воскрешение объекта оставляет значение тем же - но, к сожалению, будучи hashCode, оно подлежитстолкновения, поэтому могут иметь место ложные срабатывания, при которых объект, кажется, воскресает, когда это не так.
  • Одна последняя возможность - изменить сам объект, чтобы пометить его как завершенный (и отследить местоположение его замены)Это означает, что наблюдение этого знака на сильно достижимом объектепокажите его как воскрешенный объект, но для этого необходимо добавить дополнительное поле к любому объекту, который может быть задействован в ссылочном цикле.

Как итог, моя основная проблема заключается в «задании объектаэто в настоящее время дорабатывается, смело создавайте его копию, без случайного воскрешения каких-либо объектов, которые могут находиться в процессе его референсного цикла в процессе ».Подход, который я пытался использовать, заключается в том, что «когда объект, который потенциально может быть задействован в цикле, завершен, отслеживайте его идентичность, чтобы впоследствии его можно было заменить его копией, если он окажется доступным для другого».завершенный объект ";но ни один из пяти упомянутых выше подходов не кажется удовлетворительным.

Существует ли какой-либо другой способ отслеживать завершенные объекты, чтобы их можно было распознать в случае случайного перенаправления?Есть ли совершенно иное решение исходной проблемы - безопасного создания копии объекта во время его завершения?

Ответы [ 2 ]

3 голосов
/ 26 марта 2019

Чтобы избежать создания воскресших объектов, обычный совет (как, например, видно в в этом ответе ) состоит в том, чтобы создать свежий экземпляр объекта, а не сохранить объектсам;обычно это достигается путем копирования всех полей объекта в новый объект.

Это не «нормальный совет», даже утверждающий, что это связано с ответом.Связанный ответ начинается с « Если вам абсолютно необходимо воскресить объекты,… », что довольно ясно показывает, что это , а не совет о том, как « избегать создание воскресших объектов ».

Подход, описанный в этом ответе - это воскрешение объекта, и по иронии судьбы, это именно тот сценарий, который вы описываете как проблему , которую вы хотитерешить, воскресение объектов (тех, на которые ссылаются скопированные поля) другим финализатором объекта.

Это сохраняет все, кроме одной из проблем, связанных с финализаторами и воскресением объекта.Единственная проблема, которую он решает, заключается в том, что завершенный объект не будет снова завершен, что является наименьшей проблемой.

Когда приложение покидает объект, оно не обязательно должно быть в допустимом состоянии.Объекты должны поддерживаться в правильном состоянии только тогда, когда они предназначены для повторного использования.Например, для приложения нормально вызывать close() на объектах, представляющих ресурсы, когда с ними сделано.Но также разумно оставлять объект в середине операции, когда происходит ошибка.Ошибочное состояние результата может быть представлено другим объектом, а другой, теперь несовместимый объект не используется.

Финализатору придется иметь дело со всеми этими возможными состояниями объекта и, что еще хуже, с неиспользуемыми состояниями объекта, вызваннымифинализаторами.Как вы узнали сами, графы объектов могут быть собраны целиком, и все их финализаторы будут выполнены в произвольном порядке или даже одновременно.Поэтому ему не нужны петли и не нужны попытки воскресения, чтобы попасть в беду.Когда объект A имеет ссылку на объект B и оба имеют финализаторы, попытка очистки A может потерпеть неудачу при необходимости B в процессе, поскольку B может быть уже завершен или даже в середине одновременного завершения.

Короче говоря, финализация даже не подходит для очистки, для которой она изначально была предназначена.Вот почему метод finalize() устарел в Java 9.

Ваша попытка повторно использовать значения полей финализируемого объекта просто подливает масла в огонь.Просто подумайте о сценарии A → B выше.Когда финализатор A копирует значения полей в другой объект, это подразумевает копирование ссылки на B, и он не требует попытки финализатора B сделать то же самое.Уже достаточно, если финализатор B сделает то, для чего он предназначен, очистив связанные ресурсы, оставив B в непригодном для использования состоянии.

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

Как объяснено," объект, который в настоящее время завершается "и"безопасно »- противоречие само по себе.Не нужно взаимных попыток повторного использования, чтобы сломать его.Даже если смотреть только на оригинальную узкую формулировку проблемы, у всех ваших подходов есть проблема, заключающаяся в том, что они даже не пытаются ее предотвратить.Все они только пытаются обнаружить проблему через какое-то произвольное время после факта.

Тем не менее, нет проблемы при сравнении референта WeakReference с какой-либо другой сильной ссылкой, такой как weakReference.get() == someStrongReference.Слабая ссылка очищается только тогда, когда референт был убран сборщиком мусора, что означает, что сильная ссылка не может указать на нее, поэтому ответ false для сравнения ссылки null с someStrongReference будет правильнымответь тогда.

0 голосов
/ 26 марта 2019

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

Если предположить, что цель состоит в том, чтобы "отслеживать, как объект выглядел в тот момент, когда на него не ссылались", это можно безопасно выполнить только в том случае, если сам объект не имеет финализатора (в противном случае существует ряд труднодоступных решать проблемы, как описано в вопросе, его комментариях и других ответах). Единственная причина, по которой нам нужен финализатор, заключается в том, что мы не можем иначе добраться до объекта после того, как он стал не связанным.

Это явно плохая идея - позволить объекту стать без ссылки, а затем восстановить его из финализатора. Однако «оживление» объекта без финализатора представляет собой гораздо меньшую проблему (поскольку это эквивалентно тому, что объект вообще никогда не освобождается - он не заканчивается «частично завершенным», как это сделал бы объект с финализатором). Это может быть достигнуто путем создания отдельного объекта с финализатором и преднамеренного создания цикла ссылок между исходным объектом и отдельным, несущим финализатор объектом (который имеет просто финализатор и ссылку t на оригинал объект, больше ничего); когда объект становится иным образом не связанным, будет запущен финализатор нового объекта, но исходный объект не будет освобожден и не окажется в каком-либо неловком состоянии, связанном с финализацией.

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

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

...