JEP 421: Deprecate Finalization for Removal

AuthorsBrent Christian, Stuart Marks
OwnerBrent Christian
TypeFeature
ScopeSE
StatusClosed / Delivered
Release18
Componentcore-libs / java.lang
Discussioncore dash libs dash dev at openjdk dot java dot net
EffortS
DurationS
Reviewed byAlex Buckley, Brian Goetz, Kim Barrett
Endorsed byBrian Goetz, Mikael Vidstedt
Created2021/09/30 20:24
Updated2024/05/16 10:15
Issue8274609

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 and cleaners.

Goals

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, International Workshop on Memory Management, 1992).

Unfortunately, finalization has several critical, fundamental flaws:

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, 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 has recommended against the use of finalizers since 2008.

Real-world consequences

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

Alternative techniques

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

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:

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:

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.

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):

(Three other finalizers in java.awt.** were already terminally deprecated, and were removed from Java 18 independently of this JEP.)

In addition, we will:

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:

We do not plan to revisit the historically distinct roles played by WeakReference and PhantomReference. Conversely, we expect to update the Java Language Specification when we remove finalization, since finalizers interact with the Java Memory Model.