Deprecated Features
Below are deprecated features in Java 21.
Finalization
Finalization is a special mechanism for Java programs to interact with the JVM’s garbage collector. Finalization was introduced in JDK 1.0, so it has been there for quite a long time. The original goal of finalization was to provide a safety net to guarantee the release of native resources.
Native resources are managed by the operating system. Typical examples of native resources are file handles and native memory. Native resources are limited and must be carefully managed. It’s very important that native resources are not leaked.
Native resources are held by Java objects. These Java objects provide methods to release native resources. For example, aFile object has the close() method to close the file and release the native file handle. The close() method must be called to ensure the close of file handles.
Programming errors are inevitable. If developers forgot to call the close() method of a File object, after this File object is claimed by the garbage collector, there will be no reference to this File object and it’s impossible to release the native file handle. This native file handle is leaked.
The idea of finalization is quite simple. It relies on the interaction with the garbage collector. The garbage collector knows when a object becomes eligible for collection. The garbage collector can run some actions before it reclaims the object.
finalize in Object
The method finalize of the class Object is what Java programmers can use to interact with the garbage collector. The method finalize is called by the garbage collector on an object when there are no more references to the object. If an object needs to release native resources, the release logic can be put into the method finalize. Before the object is reclaimed by the garbage collector, native resources can be released by calling the method finalize.
Finalization sounds a brilliant idea which can guarantee the release of native resources. Unfortunately, allowing Java programs to interact with the garbage collector has never been a good idea. It turns out that finalization brings more problems than it solves.
Below is a list of common issues with finalization.
- Unpredictable latency. During the running of a Java program, it’s unpredictable when the garbage collector will run. When an object becomes eligible for reclaim, it’s unpredictable when this object will be reclaimed. The delay can be quite long, or even infinite. This means that after a object can be reclaimed and the native resource held by this object can be safely released, the native resource may be actually released after a long delay. Even worse, the finalizer may be never run by the garbage collector.
- Unconstrained behavior. You can put any code in the
finalizemethod. This may cause unexpected behavior when finalizer runs. Finalizer is called when this object becomes unreachable and ready for reclaim. If a reference is saved to the object being finalized, this object will be resurrected and becomes reachable again. - Always enabled. When finalizer is added to a class, it’s enabled for all instances of this class. Finalization cannot be cancelled, or disabled for a single object.
After finalization is deprecated, we should use different ways to release native resources. The simplest choice is to release the resource right away when it’s no longer used. For example, if we finish the use of a File object, we can call its method close() to release it. This is usually done with try-with-resources. This requires that the object’s class must implement the interface java.lang.AutoClosable. The method close() contains the logic to release native resource. try-with-resources makes sure that method close() will always be called.
The limitation of try-with-resources is that the native resource will be released after statements in try block finish execution, either normally or exceptionally. This means that the resource cannot be held for a long time.
1 public class TryWithResources {
2
3 void copyFile() throws IOException {
4 try (var input = new FileInputStream("input.txt");
5 var output = new FileOutputStream("output.txt")) {
6 input.transferTo(output);
7 }
8 }
9 }
Windows 32-bit x86 Port
Windows 32-bit x86 port of OpenJDK was deprecated for removal in Java 21. The main reason for this deprecation is that Windows 10, the last Windows operating system to support 32-bit operation, will reach End of Life in October 2025. So it’s not necessary to keep maintaining this port.
Another issue with Windows 32-bit x86 port is that the implementation of virtual threads for Windows x86-32 falls back to the use of kernel threads.
After this deprecation, attempting to configure a Windows x86-32 build will produce an error. The new build configuration option --enable-deprecated-ports=yes should be used to suppress the error and continue.
Windows 32-bit x86 port of OpenJDK was finally removed in Java 24 (JEP 479).