Again in Java 18, some features have been marked as "deprecated for removal" or deleted.
Deprecate Finalization for Removal
Finalization has existed since Java 1.0 and is intended to help avoid
resource leaks by allowing classes to implement a finalize()
method to release system resources (such as file handles or non-heap
memory) requested by the operating system.
The garbage collector calls the finalize()
method before releasing an object's memory.
That seems to be a reasonable solution. However, it has been shown that finalization has some fundamental, critical flaws:
Performance:
-
It is unpredictable when the garbage collector will clean up an object
(and whether it will do so at all). Therefore, it may happen that – after
an object is no longer referenced – it takes a very long time for
its
finalize()
method to be called (or that it is never called).
-
When the garbage collector performs a full GC, there can be noticeable
latency if many of the objects being cleaned up have
finalize()
methods.
-
The
finalize()
method is called for every instance of a class, even if it is not necessary. There is no way to specify that individual objects do not need finalization.
Security risks:
-
The
finalize()
method can execute arbitrary code, e.g., storing a reference of the object to be deleted. Thus, the garbage collector will not clean it up. If the reference to the object is later removed and the garbage collector deletes the object, itsfinalize()
method is not called again.
-
When the constructor of a class throws an exception, the object resides
on the heap. When the garbage collector later removes it, it calls
its
finalize()
method, which can then perform operations on a possibly incompletely initialized object or even store it in the object graph.
Error-proneness:
-
A
finalize()
method should always call thefinalize()
method of the parent class as well. However, the compiler does not enforce this (as it does with the constructor). Even if we write our code without errors, someone else could extend our class, override thefinalize()
method without calling the overridden method, and thereby cause a resource leak.
Multithreading:
-
The
finalize()
method is called in an unspecified thread, so thread safety of the entire object must be maintained – even in an application that does not use multithreading.
Alternatives to Finalization
The following alternatives to finalization exist:
-
The "try-with-resources" introduced in Java 7 automatically generates
a
finally
block for all classes that implement theAutoCloseable
interface, in which the correspondingclose()
methods are called. Typical static code analysis tools find and complain about code that does not generateAutoCloseable
objects inside "try-with-resources" blocks.
- Through the Cleaner API introduced in Java 9, so-called "Cleaner Actions" can be registered. The garbage collector invokes them when an object is no longer accessible (not only when it reclaims its memory). Cleaner actions do not have access to the object itself (so they cannot store a reference to it); we only need to register them for an object when that specific object needs them; and we can determine in which thread they are called.
For the above reasons and the availability of sufficient alternatives,
the finalize()
methods
in Object and numerous other classes of the JDK class library were already
marked as "deprecated" in Java 9.
JDK Enhancement Proposal 421 marks the methods in Java 18 as "deprecated for removal".
Furthermore, the VM option --finalization=disabled
is introduced, which completely disables finalization. This allows us
to test applications before migrating them to a future Java version where
finalization has been removed.
JDK Flight Recorder Event for Finalization
Not part of the above JEP is the new Flight Recorder event
"jdk.FinalizerStatistics". It is enabled by default and logs every
instantiated class with a non-empty finalize()
method. That makes it easy to identify those classes that still use a
finalizer.
These events are not triggered when finalization is disabled via --finalization=disabled
.
Terminally Deprecate Thread.stop
Thread.stop()
is marked as
"deprecated for removal" in Java 18 – finally, after being "deprecated"
since Java 1.2. Hopefully, it will be deleted in one of the following
releases – together with suspend()
and resume()
and
the corresponding ThreadGroup
methods.
(There is no JDK enhancement proposal for this change.)
Remove the Legacy PlainSocketImpl and PlainDatagramSocketImpl Implementation
In Java 13 and Java 15, the JDK developers reimplemented the Socket API and the DatagramSocket API.
The old implementations could since be reactivated via the jdk.net.usePlainSocketImpl
or jdk.net.usePlainDatagramSocketImpl
system properties.
In Java 18, the old code was removed, and the above system properties were removed
Other Changes in Java 18
In this section, you will find those changes that you will rarely encounter during your daily programming work. Nevertheless, it certainly does not hurt to skim them once.
Reimplement Core Reflection with Method Handles
If you have a lot to do with Java reflection, you will know that there is
always more than one way to go. For example, to read the private value
field of a String via reflection, there are two ways:
1. Per so-called "core reflection":
Field field = String.class.getDeclaredField("value");field.setAccessible(true);byte[] value = (byte[]) field.get(string);
Code language: Java (java)
2. Via "method handles":
VarHandle handle = MethodHandles.privateLookupIn(String.class, MethodHandles.lookup()) .findVarHandle(String.class, "value", byte[].class);byte[] value = (byte[]) handle.get(string);
Code language: Java (java)
(Important: Since Java 16, for both variants, you have to open the
package java.lang
from
the module java.base
for
the calling module, e.g., via VM option --add-opens java.base/java.lang=ALL-UNNAMED
).
There is a third form that we can't see directly: core reflection uses additional native JVM methods for the first few calls after starting the JVM and only starts compiling and optimizing the Java reflection bytecode after a while.
Maintaining all three variants means a considerable effort for the JDK
developers. Therefore, as part of JDK Enhancement Proposal 416, it was decided to reimplement the code of the reflection
classes java.lang.reflect.Method
, Field
, and Constructor
using method handles and thus reduce the development effort.
ZGC / SerialGC / ParallelGC Support String Deduplication
Since Java 18, the Z garbage collector, which was released as production-ready in Java 15 and the serial and parallel garbage collectors also support string deduplication.
String deduplication means that the garbage collector detects strings
whose value
and coder
fields
contain the same bytes. The GC deletes all but one of these byte arrays and
lets all string instances reference this single byte array.
Remember, it is not actually the Strings that are deduplicated (as the name of the feature implies), but only their byte arrays. Nothing changes in the identities of the String objects themselves.
String deduplication is disabled by default (as it is a potential attack
vector via deep reflection) and must be explicitly enabled via VM
option -XX:+UseStringDeduplication
.
(String deduplication was first released with JDK Enhancement Proposal 192 in Java 8u20 for G1. There is no separate JEP for inclusion in the ZGC, serial GC, and parallel GC in Java 18).
Allow G1 Heap Regions up to 512 MB
The G1 Garbage Collector usually determines the size of the heap regions automatically. Depending on the heap size, the size of the regions is set to a value between 1 MB and 32 MB.
You can also set the region size manually via VM option -XX:G1HeapRegionSize
. Sizes between 1 MB and 32 MB were previously allowed here as well.
In Java 18, the maximum size of regions is increased to 512 MB. This is particularly intended to help reduce heap fragmentation for very large objects.
The change only applies to the manual setting of the region size. When determined automatically by the JVM (i.e., without specifying the VM option), the maximum size remains 32 GB.