JDK-8274609 : JEP 421: Deprecate Finalization for Removal
  • Type: JEP
  • Component: core-libs
  • Sub-Component: java.lang
  • Priority: P3
  • Status: Closed
  • Resolution: Delivered
  • Fix Versions: 18
  • Submitted: 2021-09-30
  • Updated: 2024-05-16
  • Resolved: 2022-01-27
Related Reports
Blocks :  
Blocks :  
Relates :  
Relates :  
Relates :  
Relates :  
Sub Tasks
JDK-8278866 :  
Description
Summary
-------

Deprecate finalization for removal in a future release. Finalization remains enabled by default for now, but can be disabled to facilitate early testing. In a future release it will be disabled by default, and in a later release it will be removed. Maintainers of libraries and applications that rely upon finalization should consider migrating to other resource management techniques such as the [`try`-with-resources statement][try] and [cleaners][jlrCleaner].

[jlrCleaner]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/ref/Cleaner.html
[try]: https://dev.java/learn/catching-and-handling-exceptions/#anchor_6


Goals
-----

* Help developers understand the dangers of finalization.
* Prepare developers for the removal of finalization in a future version of Java.
* Provide simple tooling to help detect reliance upon finalization.


Motivation
----------

### Resource leaks

Java programs enjoy automatic memory management, wherein the JVM's garbage collector (GC) reclaims the memory used by an object when the object is no longer needed. However, some objects represent a resource provided by the operating system, such as an open file descriptor or a block of native memory. For such objects, it is insufficient merely to reclaim the object's memory; the program must also release the underlying resource back to the operating system, typically by calling the object's `close` method. If the program fails to do this before the GC reclaims the object then the information needed to release the resource is lost. The resource, still considered by the operating system to be in use, has _leaked_.

Resource leaks can be surprisingly common. Consider the following code that copies data from one file to another. In early versions of Java, developers typically used the `try-finally` construct to ensure that resources were released even if an exception occurred while copying:

    FileInputStream  input  = null;
    FileOutputStream output = null;
    try {
        input  = new FileInputStream(file1);
        output = new FileOutputStream(file2);
        ... copy bytes from input to output ...
        output.close();  output = null;
        input.close();   input  = null;
    } finally {
        if (output != null) output.close();
        if (input  != null) input.close();
    }

This code is erroneous: If copying throws an exception, and if the `output.close()` statement in the `finally` block throws an exception, then the input stream will be leaked. Handling exceptions from all possible execution paths is laborious and difficult to get right. (The fix here involves a nested `try-finally` construct, and is left as an exercise for the reader.) Even if an unhandled exception occurs only occasionally, leaked resources can build up over time.

### Finalization — and its flaws

Finalization, introduced in Java 1.0, was intended to help avoid resource leaks. A class can declare a _finalizer_ — the method `protected void finalize()` — whose body releases any underlying resources. The GC will schedule the finalizer of an unreachable object to be called before it reclaims the object's memory; in turn, the `finalize` method can take actions such as calling the object's `close` method.

At first glance, this seems like an effective safety net for preventing resource leaks: If an object containing a still-open resource becomes unreachable (the `input` object above) then the GC will schedule the finalizer, which will close the resource. In effect, finalization appropriates the power of garbage collection to manage non-memory resources (Barry Hayes, [_Finalization in the Collector Interface_](https://www.cs.tufts.edu/comp/150FP/archive/barry-hayes/finalization.pdf), International Workshop on Memory Management, 1992).

Unfortunately, finalization has several critical, fundamental flaws:

* _Unpredictable latency_ — An arbitrarily long time may pass between the moment an object becomes unreachable and the moment its finalizer is called. In fact, the GC provides no guarantee that any finalizer will ever be called.

* _Unconstrained behavior_ — Finalizer code can take any action. In particular, it can save a reference to the object being finalized, thereby _resurrecting_ the object and making it reachable once again.

* _Always enabled_ — Finalization has no explicit registration mechanism. A class with a finalizer enables finalization for every instance of the class, whether needed or not. Finalization of an object cannot be cancelled, even if it is no longer necessary for that object.

* _Unspecified threading_ — Finalizers run on unspecified threads, in an arbitrary order. Neither threading nor ordering can be controlled.

These flaws were widely recognized over twenty years ago. Advice to be careful with Java finalization appeared as early as 1998 (Bill Venners, [_Object Finalization and Cleanup: How to Design Classes for Proper Object Cleanup_](https://www.artima.com/articles/object-finalization-and-cleanup), JavaWorld, May 1998) and featured prominently in Joshua Bloch's 2001 book _Effective Java_ (Item 6: "Avoid finalizers"). The [_SEI CERT Oracle Coding Standard for Java_](https://wiki.sei.cmu.edu/confluence/display/java/SEI+CERT+Oracle+Coding+Standard+for+Java) has [recommended against the use of finalizers](https://wiki.sei.cmu.edu/confluence/display/java/MET12-J.+Do+not+use+finalizers) since 2008.

### Real-world consequences

The flaws of finalization combine to cause significant real-world problems in security, performance, reliability, and maintainability.

* _Security vulnerabilities_ — If a class has a finalizer then a new instance of that class is eligible for finalization as soon as its constructor begins to execute. If the constructor throws an exception then the new instance is not destroyed, even though it might not be completely initialized. The new instance remains eligible for finalization and its finalizer can perform arbitrary actions on the object, including resurrecting it for later use. Malicious code can use this technique to produce ill-formed objects that cause unexpected errors, or to confuse otherwise correct code into misbehaving. (The same vulnerabilities apply to objects created by deserialization.)

  Simply omitting a finalizer from a class does not prevent this problem. A subclass can declare a finalizer and thereby gain access to invalidly constructed or deserialized objects. Mitigating this problem requires additional steps that would not be necessary if finalization were not part of the platform. This problem and techniques for mitigating it are described in the [_Oracle Secure Coding Guidelines for Java SE_](https://www.oracle.com/java/technologies/javase/seccodeguide.html) (see 4-5, "[Limit the extensibility of classes and methods](https://www.oracle.com/java/technologies/javase/seccodeguide.html#4-5)" and 7-3, "[Defend against partially initialized instances of non-final classes](https://www.oracle.com/java/technologies/javase/seccodeguide.html#7-3)").

* _Performance_ — The mere presence of finalizers imposes a performance penalty: The GC must do extra work when objects are created, and also before and after finalizing them. For example, Hans Boehm described [a 7-11x slowdown when adding finalization to a class](https://www.hboehm.info/misc_slides/java_finalizers.pdf). Finalization also leads to increased pause times for throughput-oriented collectors, and increased data structure overhead for low-latency collectors.

  Some classes provide an explicit method to release resources, such as `close`, as well as a finalizer, just to be safe. If the user forgets to call `close` then the finalizer can release the resource. However, since finalizers do not support cancellation, the performance penalty is always paid, even for unnecessary finalizers for already-released resources.

* _Unreliable execution_ — Applications that use finalizers are at higher risk of intermittent and hard-to-diagnose failures. Finalizers are scheduled to run by the GC, but the GC typically operates only when necessary to satisfy memory allocation requests. If free memory is plentiful then the GC might run infrequently, inducing an arbitrary delay on finalization. When many resource-bearing objects accumulate on the heap awaiting finalization, the result can be a resource shortage that unpredictably breaks the application. In addition, finalization runs on an unspecified number of threads, so application threads might allocate resources more quickly than they can be released by finalizer threads, again leading to a resource shortage.

* _Difficult programming model_ — Finalizers are surprisingly hard to implement correctly. As a rule, the finalizer of a class must call the finalizer of its superclass, because failing to do so may cause resource leaks. The developer is responsible for remembering to call `super.finalize()` and handle any exceptions. The Java compiler does not automatically insert this call into a finalizer, in contrast to how it automatically inserts a `super(...)` call into a constructor.

  Ensuring the correctness of finalizers in your own code is insufficient to prevent problems. Other components could subclass your code and override your finalizers. An incorrect finalizer implementation on their part could effectively break previously correct code.

  Finalizers execute on one or more system threads that are not known to the application. Thus, the presence of a finalizer in an otherwise single-threaded application inherently makes it multi-threaded. This introduces the potential for deadlocks and other threading problems.

  Ultimately, finalizers increase coupling in the architecture of an application. When several components in an application have finalizers, the finalization of one component's objects might delay or otherwise interfere with the finalization of another component's objects, especially since finalizer threads are shared across components. Such interference might result in a particular kind of finalizable object accumulating on the heap, resulting in shortages of one component's resources, as described above, and eventually in deleterious effects on other components.

### Alternative techniques

Given the problems associated with finalization, developers should use alternative techniques to avoid resource leaks, namely `try`-with-resources and cleaners.

* _Try-with-resources_ — Java 7 introduced the [`try`-with-resources statement][try] as an improvement on the `try-finally` construct shown above. This statement allows resources to be used in such a way that their `close` methods are guaranteed to be called, regardless of whether exceptions occur. The earlier example can be rewritten as follows:

      try (FileInputStream input = new FileInputStream(file1);
           FileOutputStream output = new FileOutputStream(file2)) {
          ... copy bytes from input to output ...
      }

  `try`-with-resources handles all exceptional cases properly, avoiding the need for the safety net of finalization. Any resource opened and closed within a single lexical scope should be converted to work with `try`-with-resources. If the instances of a class with a finalizer can be used exclusively within `try`-with-resources statements then the finalizer is likely unnecessary, and can be removed.

* _Cleaners_ — Some resources are too long-lived to play well with `try`-with-resources, so Java 9 introduced the [_cleaner_][jlrCleaner] API to help release them. The cleaner API allows a program to register a _cleaning action_ for an object that is run some time after the object becomes unreachable. Cleaning actions avoid many of the drawbacks of finalizers:

  * _No object resurrection_ — Cleaning actions cannot access the object, so object resurrection is impossible.

  * _Enabled on-demand_ — A constructor can register a cleaning action for a new object after the object is fully initialized. This means that a cleaning action never processes an uninitialized or partially initialized object. In addition, a program can cancel an object's cleaning action so that the GC no longer needs to schedule the action.

  * _No interference_ — The developer can control which threads run cleaning actions, and so can prevent interference between cleaning actions. In addition, an erroneous or malicious subclass cannot interfere with cleaning actions set up by its superclass.

  However, like finalizers, cleaning actions are scheduled by the GC, so they may suffer from unbounded delays. Thus the cleaner API should not be used in situations where the timely release of a resource is required. In addition, cleaners should not be used to replace a finalizer that serves only as a safety net to protect against uncaught exceptions or missing calls of `close()` methods; in such cases, investigate using `try`-with-resources before converting the finalizer to a cleaner.

  One scenario in which cleaners, despite their latency, are valuable to advanced developers, is in implementing APIs that do not permit explicit `close()` methods. Consider a version of the `BigInteger` class that uses native memory in its underlying implementation. Adding a `close()` method to the `BigInteger` class would fundamentally change its programming model and would preclude certain optimizations. Since user code cannot `close` a `BigInteger`, the implementor must rely on the GC to schedule a cleaning action to release the native memory. The implementor can balance the interests of developers, who benefit from a simpler API, against runtime overhead. (The incubating Foreign Function & Memory API ([JEP 419](https://openjdk.java.net/jeps/419)), which provides a better way to access native memory, supports the use of cleaners to deallocate native memory and avoid resource leaks.)

### Summary

Finalization has serious flaws that have been widely recognized for decades. Its presence in the Java Platform burdens the entire ecosystem because it exposes all library and application code to security, reliability, and performance risks. It also imposes ongoing maintenance and development costs on the JDK, particularly on the GC implementations. To move the Java Platform forward, we will deprecate finalization for removal.


Description
-----------

We propose to:

- Add a command-line option to disable finalization, so that no finalizers are ever scheduled by the GC to run, and
- Deprecate all finalizers, and finalization-related methods, in the standard Java API.

Note that finalization is distinct from both the `final` modifier and the `finally` block of the `try-finally` construct. No changes are proposed to either `final` or `try-finally`.

### Command-line option to disable finalization

Finalization remains enabled by default in JDK 18. A new command-line option `--finalization=disabled` disables finalization. A JVM launched with  `--finalization=disabled` will not run any finalizers — not even those declared within the JDK itself.

You can use the option to help determine if your application relies on finalization, and to test how it will behave once finalization is removed. For example, you could first run an application load test _without_ the option, so that finalization is enabled, and record metrics such as:

- Memory profiles for Java heap and/or native memory,
- Statistics from `BufferPoolMXBean` and `UnixOperatingSystemMXBean::getOpenFileDescriptorCount`, and
- The `jdk.FinalizerStatistics` event from [JDK Flight Recorder (JFR)][jfr]. This event provides data about finalizer use at run time, as discussed in the [JDK 18 release note](https://www.oracle.com/java/technologies/javase/18-relnote-issues.html#JDK-8266936). JFR can be used as follows:  
    `java -XX:StartFlightRecording:filename=recording.jfr ...`  
    `jfr print --events jdk.FinalizerStatistics recording.jfr`  

You could then rerun the load test _with_ the option, so that finalization is disabled. A significant degradation in the reported metrics, or an error or a crash, would indicate a need to investigate where the application relies on finalization. Substantially similar results between runs would provide some assurance that the application will not be impacted by the eventual removal of finalization.

Disabling finalization may have unpredictable consequences, so you should use the option only for testing and not in a production environment.

If finalization is disabled, JFR will not emit any `jdk.FinalizerStatistics` events. Also, `jcmd GC.finalizer_info` will report that finalization is disabled (instead of reporting the number of objects pending finalization).

For completeness, `--finalization=enabled` is supported.

[jfr]: https://docs.oracle.com/en/java/java-components/jdk-mission-control/8/user-guide/using-jdk-flight-recorder.html#GUID-D38849B6-61C7-4ED6-A395-EA4BC32A9FD6


### Deprecating finalizers in the standard Java API

We will terminally deprecate these methods in the `java.base` and `java.desktop` modules, by annotating them with `@Deprecated(forRemoval=true)`:

* `java.lang.Object.finalize()`
* `java.lang.Enum.finalize()`
* `java.awt.Graphics.finalize()`
* `java.awt.PrintJob.finalize()`
* `java.util.concurrent.ThreadPoolExecutor.finalize()`
* `javax.imageio.spi.ServiceRegistry.finalize()`
* `javax.imageio.stream.FileCacheImageInputStream.finalize()`
* `javax.imageio.stream.FileImageInputStream.finalize()`
* `javax.imageio.stream.FileImageOutputStream.finalize()`
* `javax.imageio.stream.ImageInputStreamImpl.finalize()`
* `javax.imageio.stream.MemoryCacheImageInputStream.finalize()`

(Three other finalizers in `java.awt.**` were already terminally deprecated, and were [removed from Java 18](https://bugs.openjdk.java.net/browse/JDK-8273102) independently of this JEP.)

In addition, we will:

* Terminally deprecate `java.lang.Runtime.runFinalization()` and `java.lang.System.runFinalization()`. These methods serve no purpose without finalization.

* Deprecate the `getObjectPendingFinalizationCount()` method in the `java.lang.management.MemoryMXBean` interface of the `java.management` module, by annotating it with `@Deprecated(forRemoval=false)`.

  This method is not part of the finalization mechanism, but rather queries the operation of the mechanism. We will deprecate this method because developers should avoid using it: Once finalization is removed, no objects will ever be pending and the method will always return zero. We will not terminally deprecate the method because we do not plan to remove it. `MemoryMXBean` is an interface, so removing a method could adversely impact a wide variety of independent implementations.


Future Work
-----------

We expect a lengthy transition period before removing finalization. This will provide time for developers to assess whether their systems rely upon finalization and to migrate their code as necessary. We also envision several other steps:

* The JDK itself makes significant use of finalizers. Some have already been removed or converted to use `Cleaner`. [JDK-8253568](https://bugs.openjdk.java.net/browse/JDK-8253568) tracks the removal of the remaining finalizers.

* Well-known libraries such as Netty, Log4j, Guava, and Apache Commons use finalizers. We will work with their maintainers to ensure that these libraries can safely migrate away from finalization.

* We will publish documentation to help developers with several aspects of migrating away from finalization, such as:
    * How to find finalizers in library and application code (for example, using `jdeprscan`),
    * How to determine if a system relies on finalization, and
    * How to convert code with finalizers to use `try`-with-resources or cleaners.

* Future versions of the JDK may include some or all of the following changes:
    * Issue a warning at runtime when finalizers are in use,
    * Disable finalization by default, and require the `--finalization=enabled` option to re-enable it,
    * Later, remove the finalization mechanism, while leaving non-functioning APIs in place, and,
    * Finally, after most code has been migrated, remove the above terminally deprecated methods, including `Object::finalize`.

We do not plan to revisit the historically distinct roles played by `WeakReference` and `PhantomReference`. Conversely, we expect to update the [Java Language Specification](https://docs.oracle.com/javase/specs/jls/se17/html/jls-12.html#jls-12.6) when we remove finalization, since finalizers interact with the Java Memory Model.


Comments
Updated JFR event Release Note link
02-09-2022

Added mention of jcmd GC.finalizer_info.
16-12-2021

Add name of the option to disable finalization, --finalization=disabled
11-12-2021

Fix javadoc links
12-10-2021