Sunday, May 29, 2022

Java_18_Features - Deprecations and Deletions

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, its finalize() 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:

  • finalize() method should always call the finalize() 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 the finalize() 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 the AutoCloseable interface, in which the corresponding close() methods are called. Typical static code analysis tools find and complain about code that does not generate AutoCloseable 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.MethodField, 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.


You may also like

Kubernetes Microservices
Python AI/ML
Spring Framework Spring Boot
Core Java Java Coding Question
Maven AWS