Terminology
The terminology of ''finalizer'' and ''finalization'' versus ''destructor'' and ''destruction'' varies between authors and is sometimes unclear. In common use, a ''destructor'' is a method called deterministically on object destruction, and the archetype is C++ destructors; while a finalizer is called non-deterministically by the garbage collector, and the archetype isfinalize
methods.
For languages that implement garbage collection via reference counting, terminology varies, with some languages such as Use
Finalization is mostly used for cleanup, to release memory or other resources: to deallocate memory allocated via manual memory management; to clear references if reference counting is used (decrement reference counts); to release resources, particularly in the resource acquisition is initialization (RAII) idiom; or to unregister an object. The amount of finalization varies significantly between languages, from extensive finalization in C++, which has manual memory management, reference counting, and deterministic object lifetimes; to often no finalization in Java, which has non-deterministic object lifetimes and is often implemented with a tracing garbage collector. It is also possible for there to be little or no explicit (user-specified) finalization, but significant implicit finalization, performed by the compiler, interpreter, or runtime; this is common in case of automatic reference counting, as in the CPython reference implementation of Python, or inByteBuffer
objects in New I/O (NIO). This latter can cause problems due to the garbage collector being unable to track these external resources, so they will not be collected aggressively enough, and can cause out-of-memory errors due to exhausting unmanaged memory – this can be avoided by treating native memory as a resource and using the dispose pattern, as discussed below.
Finalizers are generally both much less necessary and much less used than destructors. They are much less necessary because garbage collection automates Syntax
Programming languages that use finalizers include C++/CLI, C#, Clean, Go,finalize
, which overrides the Object.finalize
method.java.lang, Class Object__del__
.
In Perl, a finalizer is a method called DESTROY
.
~
prefixed, as in ~Foo
– this is the same syntax as a C++ ''destructor'', and these methods were originally called "destructors", by analogy with C++, despite having different behavior, but were renamed to "finalizers" due to the confusion this caused.
In C++/CLI, which has both destructors and finalizers, a destructor is a method whose name is the class name with ~
prefixed, as in ~Foo
(as in C#), and a finalizer is a method whose name is the class name with !
prefixed, as in !Foo
.
In Go finalizers are applied to a single pointer by calling the runtime.SetFinalizer
function in the standard library.
Implementation
A finalizer is called when an object is garbage collected – after an object has become garbage (unreachable), but before its memory is deallocated. Finalization occurs non-deterministically, at the discretion of the garbage collector, and might never occur. This contrasts with destructors, which are called deterministically as soon as an object is no longer in use, and are always called, except in case of uncontrolled program termination. Finalizers are most frequently instance methods, due to needing to do object-specific operations. The garbage collector must also account for the possibility of object resurrection. Most commonly this is done by first executing finalizers, then checking whether any objects have been resurrected, and if so, aborting their destruction. This additional check is potentially expensive – a simple implementation re-checks all garbage if even a single object has a finalizer – and thus both slows down and complicates garbage collection. For this reason, objects with finalizers may be collected less frequently than objects without finalizers (only on certain cycles), exacerbating problems caused by relying on prompt finalization, such as resource leaks. If an object is resurrected, there is the further question of whether its finalizer is called again, when it is next destroyed – unlike destructors, finalizers are potentially called multiple times. If finalizers are called for resurrected objects, objects may repeatedly resurrect themselves and be indestructible; this occurs in the CPython implementation of Python prior to Python 3.4, and in CLR languages such as C#. To avoid this, in many languages, including Java, Objective-C (at least in recent Apple implementations), and Python from Python 3.4, objects are finalized at most once, which requires tracking if the object has been finalized yet. In other cases, notably CLR languages like C#, finalization is tracked separately from the objects themselves, and objects can be repeatedly registered or deregistered for finalization.Problems
Depending on the implementation, finalizers can cause a significant number of problems, and are thus strongly discouraged by a number of authorities.MET12-J. Do not use finalizersResource management
A common anti-pattern is to use finalizers to release resources, by analogy with the resource acquisition is initialization (RAII) idiom of C++: acquire a resource in the initializer (constructor), and release it in the finalizer (destructor). This does not work, for a number of reasons. Most basically, finalizers may never be called, and even if called, may not be called in a timely manner – thus using finalizers to release resources will generally cause resource leaks. Further, finalizers are not called in a prescribed order, while resources often need to be released in a specific order, frequently the opposite order in which they were acquired. Also, as finalizers are called at the discretion of the garbage collector, they will often only be called under managed memory pressure (when there is little managed memory available), regardless of resource pressure – if scarce resources are held by garbage but there is plenty of managed memory available, garbage collection may not occur, thus not reclaiming these resources. Thus instead of using finalizers for automatic resource management, in garbage-collected languages one instead must manually manage resources, generally by using the dispose pattern. In this case resources may still be acquired in the initializer, which is called explicitly on object instantiation, but are released in the dispose method. The dispose method may be called explicitly, or implicitly by language constructs such as C#'susing
, Java's try
-with-resources, or Python's with
.
However, in certain cases both the dispose pattern and finalizers are used for releasing resources. This is mostly found in CLR languages such as C#, where finalization is used as a backup for disposal: when a resource is acquired, the acquiring object is queued for finalization so that the resource is released on object destruction, even if the resource is not released by manual disposal.
Deterministic and non-deterministic object lifetimes
In languages with deterministic object lifetimes, notably C++, resource management is frequently done by tying resource possession lifetime to object lifetime, acquiring resources during initialization and releasing them during finalization; this is known as resource acquisition is initialization (RAII). This ensures that resource possession is a class invariant, and that resources are released promptly when the object is destroyed. However, in languages with non-deterministic object lifetimes – which include all major languages with garbage collection, such as C#, Java, and Python – this does not work, because finalization may not be timely or may not happen at all, and thus resources may not be released for a long time or even at all, causing resource leaks. In these languages resources are instead generally managed manually via the dispose pattern: resources may still be acquired during initialization, but are released by calling adispose
method. Nevertheless, using finalization for releasing resources in these languages is a common anti-pattern, and forgetting to call dispose
will still cause a resource leak.
In some cases both techniques are combined, using an explicit dispose method, but also releasing any still-held resources during finalization as a backup. This is commonly found in C#, and is implemented by registering an object for finalization whenever a resource is acquired, and suppressing finalization whenever a resource is released.
Object resurrection
If user-specified finalizers are allowed, it is possible for finalization to cause object resurrection, as the finalizers can run arbitrary code, which may create references from live objects to objects being destroyed. For languages without garbage collection, this is a severe bug, and causes dangling references and memory safety violations; for languages with garbage collection, this is prevented by the garbage collector, most commonly by adding another step to garbage collection (after running all user-specified finalizers, check for resurrection), which complicates and slows down garbage collection. Further, object resurrection means that an object may not be destroyed, and in pathological cases an object can always resurrect itself during finalization, making itself indestructible. To prevent this, some languages, like Java and Python (from Python 3.4) only finalize objects once, and do not finalize resurrected objects. Concretely this is done by tracking if an object has been finalized on an object-by-object basis. Objective-C also tracks finalization (at least in recent Apple versions) for similar reasons, treating resurrection as a bug. A different approach is used in theGC
module. Finalization can be prevented by calling GC.SuppressFinalize
, which dequeues the object, or reactivated by calling GC.ReRegisterForFinalize
, which enqueues the object. These are particularly used when using finalization for resource management as a supplement to the dispose pattern, or when implementing an object pool.
Contrast with initialization
Finalization is formally complementary to initialization – initialization occurs at the start of lifetime, finalization at the end – but differs significantly in practice. Both variables and objects are initialized, mostly to assign values, but in general only objects are finalized, and in general there is no need to clear values – the memory can simply be deallocated and reclaimed by the operating system. Beyond assigning initial values, initialization is mostly used to acquire resources or to register an object with some service (like an event handler). These actions have symmetric release or unregister actions, and these can symmetrically be handled in a finalizer, which is done in RAII. However, in many languages, notably those with garbage collection, object lifetime is asymmetric: object creation happens deterministically at some explicit point in the code, but object destruction happens non-deterministically, in some unspecified environment, at the discretion of the garbage collector. This asymmetry means that finalization cannot be effectively used as the complement of initialization, because it does not happen in a timely manner, in a specified order, or in a specified environment. The symmetry is partially restored by also disposing of the object at an explicit point, but in this case disposal and destruction do not happen at the same point, and an object may be in a "disposed but still alive" state, which weakens the class invariants and complicates use. Variables are generally initialized at the start of their lifetime, but not finalized at the end of their lifetime – though if a variable has an object as its value, the ''object'' may be finalized. In some cases variables are also finalized: GCC extensions allow finalization of variables. Connection with finally
finally
construct both fulfill similar purposes: performing some final action, generally cleaning up, after something else has finished. They differ in when they occur – a finally
clause is executed when program execution leaves the body of the associated try
clause – this occurs during stack unwind, and there is thus a stack of pending finally
clauses, in order – while finalization occurs when an object is destroyed, which happens depending on the memory management method, and in general there is simply a set of objects awaiting finalization – often on the heap – which need not happen in any specific order.
However, in some cases these coincide. In C++, object destruction is deterministic, and the behavior of a finally
clause can be produced by having a local variable with an object as its value, whose scope is a block corresponds to the body of a try
clause – the object is finalized (destructed) when execution exits this scope, exactly as if there were a finally
clause. For this reason, C++ does not have a finally
construct – the difference being that finalization is defined in the class definition as the destructor method, rather than at the call site in a finally
clause.
Conversely, in the case of a finally
clause in a coroutine, like in a Python generator, the coroutine may never terminate – only ever yielding – and thus in ordinary execution the finally
clause is never executed. If one interprets instances of a coroutine as objects, then the finally
clause can be considered a finalizer of the object, and thus can be executed when the instance is garbage collected. In Python terminology, the definition of a coroutine is a generator ''function,'' while an instance of it is a generator ''iterator,'' and thus a finally
clause in a generator function becomes a finalizer in generator iterators instantiated from this function.
History
The notion of finalization as a separate step in object destruction dates to , by analogy with the earlier distinction of initialization in object construction in . Literature prior to this point used "destruction" for this process, not distinguishing finalization and deallocation, and programming languages dating to this period, like C++ and Perl, use the term "destruction". The terms "finalize" and "finalization" are also used in the influential book '' Design Patterns'' (1994)."Every new class has a fixed implementation overhead (initialization, finalization, etc.).", "''destructor'' In C++, an operation that is automatically invoked to finalize an object that is about to be deleted." The introduction of Java in 1995 containedfinalize
methods, which popularized the term and associated it with garbage collection, and languages from this point generally make this distinction and use the term "finalization", particularly in the context of garbage collection.
Notes
References
Further reading
* * *External links
*