В объектно-ориентированных языках программирования со сборкой мусора воскрешение объекта происходит , когда объект становится доступным (другими словами, он больше не является мусором) в процессе уничтожения объекта , как побочный эффект выполнения финализатора .
Воскрешение объекта вызывает ряд проблем, в частности, возможность воскрешения объекта (даже если она не происходит) значительно усложняет и замедляет сбор мусора и является основной причиной того, что финализаторы не рекомендуются. Языки по-разному справляются с воскрешением объектов. В редких случаях воскрешение объекта используется для реализации определенных шаблонов проектирования, в частности пула объектов , [1] тогда как в других обстоятельствах воскрешение является нежелательной ошибкой, вызванной ошибкой в финализаторах, и в целом воскрешение не рекомендуется. [2]
Воскрешение объекта происходит посредством следующего процесса. Во-первых, объект становится мусором, когда он больше не доступен из программы, и его можно собрать (уничтожить и освободить). Затем, во время уничтожения объекта, прежде чем сборщик мусора освободит объект, может быть запущен метод финализатора , который, в свою очередь, может сделать этот объект или другой объект мусора (доступный из объекта с помощью финализатора) снова доступным путем создания ссылок на него, как финализатор может содержать произвольный код. Если это произойдет, объект, на который ссылаются (который не обязательно является финализированным объектом), больше не является мусором и не может быть освобожден, поскольку в противном случае ссылки на него станут висячими ссылками и вызовут ошибки при использовании, обычно сбой программы или непредсказуемое поведение. Вместо этого, чтобы обеспечить безопасность памяти , объект возвращается к жизни или воскрешается.
Чтобы обнаружить это, сборщик мусора обычно выполняет двухэтапную сборку при наличии финализаторов: сначала финализирует весь мусор, имеющий финализатор, а затем повторно проверяет весь мусор (или весь мусор, доступный из объектов с финализаторами). на случай, если финализаторы воскресили какой-то мусор. Это увеличивает накладные расходы и задерживает освобождение памяти.
С воскрешенным объектом можно обращаться так же, как и с другими объектами, или обращаться с ним особым образом. Во многих языках, особенно в C#, Java и Python (начиная с Python 3.4), объекты финализируются только один раз, чтобы избежать возможности повторного воскрешения объекта или даже его неразрушимости; в C# объекты с финализаторами по умолчанию финализируются только один раз, но могут быть перерегистрированы для финализации. В других случаях восстановленные объекты считаются ошибками, особенно в Objective-C; или обрабатываются идентично другим объектам, особенно в Python до Python 3.4.
Воскресший объект иногда называютзомби-объект илизомби, но этот термин используется для различных состояний объекта, связанных с уничтожением объекта, причем его использование зависит от языка и автора.«объект-зомби» имеет вObjective-C, которое подробно описано ниже. Объекты-зомби в чем-то аналогичныпроцессам-зомбитем, что они претерпели изменение состояния завершения и близки к освобождению, но детали существенно отличаются.
В .NET Framework , особенно в C# и VB.NET, «воскрешение объекта» вместо этого относится к состоянию объекта во время финализации: объект возвращается к жизни (из недоступности), запускается финализатор, а затем возвращается в недоступен (и больше не регистрируется для будущей доработки). В .NET то, какие объекты нуждаются в финализации, не отслеживается пообъектно, а вместо этого сохраняется в «очереди» финализации, [a] поэтому вместо понятия воскрешенных объектов в смысле этой статьи говорят об объектах. «в очереди на завершение». Кроме того, объекты можно повторно поставить в очередь для финализации с помощью GC.ReRegisterForFinalize
, стараясь не увеличивать количество объектов в очереди. [2]
Существует два основных способа, с помощью которых объект может воскресить себя или другой объект: создав ссылку на себя в объекте, до которого он может добраться (мусор недоступен, но мусор может ссылаться на объекты, не являющиеся мусором), или путем создания ссылки в окружение ( глобальные переменные или, в некоторых случаях, статические переменные или переменные в замыкании ). Далее следуют примеры Python для обоих объектов, воскрешающих себя. Объект также может воскрешать другие объекты, если оба они собираются в данном цикле сборки мусора с помощью одних и тех же механизмов.
Воскрешает себя, создавая ссылку в объекте, которого он может достичь:
класс Clingy : def __init__ ( self , ref = None ) -> None : self . ссылка = ссылка def __del__ ( self ): если self . ссылка : сам . исх . ref = self print ( «Не оставляй меня!» )a = Clingy ( Clingy ()) # Создайте связанный список из двух элементов, # на который ссылается |a| а . исх . ref = a # Создать цикл a . ref = None # Очистка ссылки из первого узла # на второй делает второй мусор a . ссылка = Нет
Воскрешает себя, создавая ссылку в глобальной среде:
c = Нет class Immortal : def __del__ ( self ): global c c = self print ( «Я еще не умер». )c = Бессмертный () c = Нет # Очистка |c| превращает объект в мусор c = None
В приведенных выше примерах в CPython до 3.4 финализаторы будут запускаться повторно, и объекты не будут подвергаться сборке мусора, тогда как в CPython 3.4 и более поздних версиях финализаторы будут вызываться только один раз, и объекты будут подвергаться сборке мусора. во второй раз они становятся недоступными.
Воскрешение объекта вызывает большое количество проблем.
GC.ReRegisterForFinalize
. [1]В языках принято несколько различных методов восстановления объектов, чаще всего с помощью двухфазной сборки мусора при наличии финализаторов, чтобы предотвратить висячие ссылки; и завершая объекты только один раз, в частности, отмечая объекты как завершенные (с помощью флага), чтобы гарантировать возможность уничтожения объектов.
Java не освободит объект, пока не докажет, что объект снова недоступен, но не будет запускать финализатор более одного раза. [3]
В Python до Python 3.4 стандартная реализация CPython обрабатывала воскрешенные объекты идентично другим объектам (которые никогда не были финализированы), что делало возможным создание неразрушимых объектов. [4] Кроме того, циклы сборки мусора, содержащие объект с финализатором, не будут выполняться, чтобы избежать возможных проблем с воскрешением объекта. Начиная с Python 3.4, поведение во многом такое же, как и в Java: объекты [b] финализируются только один раз (помечаются как «уже финализированные»), сборка мусора в циклах происходит в два этапа, причем второй этап проверяет наличие восстановленных объектов. [5] [6]
Objective-C 2.0 переводит воскресшие объекты в состояние «зомби», где они регистрируют все отправленные им сообщения, но больше ничего не делают. [7] См. также «Автоматический подсчет ссылок: обнуление слабых ссылок» для обработки слабых ссылок .
В .NET Framework, особенно в C# и VB.NET, завершение объекта определяется «очередью» завершения [a] , которая проверяется во время уничтожения объекта. Объекты с финализатором помещаются в эту очередь при создании и удаляются из очереди при вызове финализатора, но могут быть исключены из очереди вручную (до финализации) с помощью SuppressFinalize
или повторно поставлены в очередь с помощью ReRegisterForFinalize
. Таким образом, по умолчанию объекты с финализаторами финализируются не более одного раза, но эту финализацию можно подавить или объекты могут финализироваться несколько раз, если они воскрешаются (снова становятся доступными), а затем повторно ставятся в очередь для финализации. Кроме того, слабые ссылки по умолчанию не отслеживают воскрешение, то есть слабая ссылка не обновляется, если объект воскрешается; они называются короткими слабыми ссылками , а слабые ссылки, отслеживающие воскрешение, называются длинными слабыми ссылками . [8]
Воскрешение объектов полезно для обработки пула часто используемых объектов, но оно запутывает код и делает его более запутанным. [3] Его следует использовать только для объектов, которые могут использоваться часто и где их строительство/разрушение требует много времени. Примером может быть массив случайных чисел, в котором большое количество из них создается и уничтожается за короткое время, но на самом деле одновременно используется только небольшое их число. При воскрешении объектов метод объединения позволит сократить ненужные затраты на создание и уничтожение. Здесь менеджер пула получит информацию о своем стеке объектов в виде ссылки на объект, если он в данный момент подлежит уничтожению. Менеджер пула сохранит объект для повторного использования позже. [9]
GC.SuppressFinalization
.Объект, допущенный к сбору мусора, может перестать соответствовать критериям и вернуться к нормальной жизни. В методе Finalize() вы можете присвоить это ссылочной переменной и предотвратить сбор этого объекта — действие, которое многие разработчики называют воскрешением. /Метод Finalize() никогда не вызывается JVM более одного раза для любого данного объекта. JVM не будет снова вызывать метод Finalize() после воскрешения (поскольку метод Finalize() уже выполнялся для этого объекта).
Воскрешение объектов — это продвинутый метод, который, вероятно, будет полезен только в необычных сценариях, например, когда вы реализуете пул объектов, создание и уничтожение которых занимает много времени. ... Демонстрационное приложение ObjectPool показывает, что менеджер пула объектов может повысить производительность, когда часто создается и уничтожается множество объектов. Предположим, у вас есть класс RandomArray, который инкапсулирует массив случайных чисел. Основная программа создает и уничтожает тысячи объектов RandomArray, хотя в данный момент активны лишь несколько объектов. Поскольку класс создает случайный массив в своем методе-конструкторе (операция, требующая много времени), эта ситуация идеальна для метода объединения. ... Важным моментом в методе объединения в пул является то, что класс PoolManager содержит ссылку на неиспользуемые объекты в пуле (в объекте PooledObjects Stack), но не на объекты, используемые основной программой. Фактически, последние объекты поддерживаются только за счет ссылок в основной программе. Когда основная программа присваивает объекту RandomArray значение Nothing (или позволяет ему выйти за пределы области видимости) и происходит сборка мусора, сборщик мусора вызывает метод Finalize объекта. Таким образом, код внутри метода Finalize RandomArray имеет возможность воскресить себя, сохранив ссылку на себя в структуре PooledObjects PoolManager. Поэтому, когда функция NewRandomArray вызывается снова, объект PoolManager может вернуть клиенту объект из пула, не проходя трудоемкий процесс создания нового.