Object resurrection
In object-oriented programming languages with garbage collection, object resurrection is when an object comes back to life during the process of object destruction, as a side effect of a finalizer being executed.
Object resurrection causes a number of [|problems], particularly that the possibility of object resurrection – even if it does not occur – makes garbage collection significantly more complicated and slower, and is a major reason that finalizers are discouraged. Languages deal with object resurrection in various ways, as [|solutions] to these problems. In rare circumstances, object resurrection is used to implement certain design patterns, notably an object pool, while in other circumstances resurrection is an undesired bug caused by an error in finalizers, and in general resurrection is discouraged.
Process
Object resurrection occurs via the following process. First, an object becomes garbage when it is no longer reachable from the program, and may be collected. Then, during object destruction, before the garbage collector deallocates the object, a finalizer method may be run, which may in turn make that object or another garbage object reachable again by creating references to it, as a finalizer may contain arbitrary code. If this happens, the referenced object – which is not necessarily the finalized object – is no longer garbage, and cannot be deallocated, as otherwise the references to it would become dangling references and cause errors when used, generally program crash or unpredictable behavior. Instead, in order to maintain memory safety, the object is returned to life or resurrected.In order to detect this, a garbage collector will generally do two-phase collection in the presence of finalizers: first finalize any garbage that has a finalizer, and then re-check all garbage, in case the finalizers have resurrected some garbage. This adds overhead and delays memory reclamation.
Resurrected objects
A resurrected object may be treated the same as other objects, or may be treated specially. In many languages, notably C#, Java, and Python, objects are only finalized once, to avoid the possibility of an object being repeatedly resurrected or even being indestructible; in C# objects with finalizers by default are only finalized once, but can be re-registered for finalization. In other cases resurrected objects are considered errors, notably in Objective-C; or treated identically to other objects, notably in Python prior to Python 3.4.A resurrected object is sometimes called a or zombie, but this term is used for various object states related to object destruction, with usage depending on language and author. A "zombie object" has a specialized meaning in Objective-C, however, which is detailed below. Zombie objects are somewhat analogous to zombie processes, in that they have undergone a termination state change and are close to deallocation, but the details are significantly different.
Variants
In the.NET Framework, notably C# and VB.NET, "object resurrection" instead refers to the state of an object during finalization: the object is brought back to life, the finalizer is run, and then returned to being inaccessible. In.NET, which objects need finalization is not tracked object-by-object, but instead is stored in a finalization "queue", so rather than a notion of resurrected objects in the sense of this article, one speaks of objects "queued for finalization". Further, objects can be re-enqueued for finalization viaGC.ReRegisterForFinalize
, taking care to not multiply enqueue objects.Mechanism
There are two main ways that an object can resurrect itself or another object: by creating a reference to itself in an object that it can reach, or by creating a reference in the environment. Python examples of both follow, for an object resurrecting itself. It is also possible for an object to resurrect other objects if both are being collected in a given garbage collection cycle, by the same mechanisms.Resurrects itself by creating a reference in an object it can reach:
class Clingy:
def __init__ -> None:
self.ref = ref
def __del__:
if self.ref:
self.ref.ref = self
a = Clingy) # Create a 2-element linked list,
# referenced by |a|
a.ref.ref = a # Create a cycle
a.ref = None # Clearing the reference from the first node
# to the second makes the second garbage
a.ref = None
Resurrects itself by creating a reference in the global environment:
c = None
class Immortal:
def __del__:
global c
c = self
c = Immortal
c = None # Clearing |c| makes the object garbage
c = None
In the above examples, in CPython prior to 3.4, these will run finalizers repeatedly, and the objects will not be garbage-collected, while in CPython 3.4 and later, the finalizers will only be called once, and the objects will be garbage-collected the second time they become unreachable.
Problems
Object resurrection causes a large number of problems.;Complicates garbage collection
;Indestructible objects
;Accidental resurrection and leaks
;Inconsistent state and reinitialization
;Unique finalization or re-finalization
Solutions
Languages have adopted several different methods for coping with object resurrection, most commonly by having two-phase garbage collection in the presence of finalizers, to prevent dangling references; and by only finalizing objects once, particularly by marking objects as having been finalized, to ensure that objects can be destroyed.Java will not free the object until it has proven that the object is once again unreachable, but will not run the finalizer more than once.
In Python, prior to Python 3.4, the standard CPython implementation would treat resurrected objects identically to other objects, making indestructible objects possible. Further, it would not garbage collect cycles that contained an object with a finalizer, to avoid possible problems with object resurrection. Starting in Python 3.4, behavior is largely the same as Java: objects are only finalized once, garbage collection of cycles is in two phases, with the second phase checking for resurrected objects.
Objective-C 2.0 will put resurrected objects into a "zombie" state, where they log all messages sent to them, but do nothing else. See also Automatic Reference Counting: Zeroing Weak References for handling of weak references.
In the.NET Framework, notably C# and VB.NET, object finalization is determined by a finalization "queue", which is checked during object destruction.
Objects with a finalizer are placed in this queue on creation, and dequeued when the finalizer is called, but can be manually dequeued with
SuppressFinalize
or re-enqueued with ReRegisterForFinalize
. Thus by default objects with finalizers are finalized at most once, but this finalization can be suppressed, or objects can be finalized multiple times if they are resurrected and then re-enqueued for finalization. Further, weak references by default do not track resurrection, meaning a weak reference is not updated if an object is resurrected; these are called short weak references, and weak references that track resurrection are called long weak references.