Java 14 : what’s new ?

Java 14 : what’s new ?

Now that Java 14 is features complete (Rampdown Phase One at the day of writing), it’s time to walk throught all it’s functionalities that brings to us, developers, this new version.

This article is part of a series on what’s new on the last versions of Java, for those who wants to read the others, here are the links : Java 13, Java 12, Java 11Java 10, and Java 9.

As opposite to the previous Java versions, the version 14 brings a lot of functionalities, according to me, it’s a version that we can qualify major like Java 5 or 8 ! Records, Pattern Matching, Switch Expression, Text Block, … A lot of good stuff in this version 😉

JEP 361: Switch Expressions (Standard)

Switch Expressions goes out of preview and becomes a standard functionality !

The new keyword yield has been validated.

More info in the JEP: https://openjdk.java.net/jeps/361

If you had missed this functionality in the previous versions, here is an article that describe it in details: Definitive Guide To Switch Expressions

JEP 368: Text Blocks (Second Preview)

Text Blocks : the possibility to write String Literals on multiple lines is still in preview, with the addition of two new escape sequence : '\' and '\s'.

'\' allows to write on multiple lines a String that must be write on a single line (like in shell).

String text = """
                Lorem \
                ipsum \
                dolor \
                """;
System.out.println(text); // Lorem impsum dolor

'\s' allow to add a space (\u0020)

String colors = """
    red  \s
    green\s
    blue \s
    """;

More info in the JEP: https://openjdk.java.net/jeps/368
If you had missed this functionality, it is described in my article on Java 13.

JEP 358: Helpful NullPointerExceptions

NullPointerExceptions (NPE) are very common on Java, and the error message is often not very useful as it only points to the line in which the exception occurs, and not exactly on the instruction / code part that generates this exception.

SAP implemented in it’s own JVM (since 2006!) an enhanced version of the messages of the NPE. With this experience, the implementation has been enhanced inside OpenJDK. To my great regret, we need to add an option to the command line, -XX:+ShowCodeDetailsInExceptionMessages, to enable this usefull feature.

For comparison, here are the standard error messages for NullPointerException.
You can see that we didn’t know which object is null: a ? a.s ?

$ jshell
|  Welcome to JShell -- Version 14-ea
|  For an introduction type: /help intro

jshell> public class A { public String s;}
|  created class A

jshell> A a;
a ==> null

jshell> a.s.toString()
|  Exception java.lang.NullPointerException
|        at (#3:1)

jshell> a.s = "toto";
|  Exception java.lang.NullPointerException
|        at (#4:1)

jshell> a = new A();
a ==> A@3f8f9dd6

jshell> a.s.toString()
|  Exception java.lang.NullPointerException
|        at (#6:1)

jshell> 

Executing the same lines but with useful NPE enabled will give us precisely which object is null, and which operation generated a NPE on this object (read, write, method call).

$ jshell -R-XX:+ShowCodeDetailsInExceptionMessages
|  Welcome to JShell -- Version 14-ea
|  For an introduction type: /help intro

jshell> public class A { public String s;}
|  created class A

jshell> A a;
a ==> null

jshell> a.s.toString()
|  Exception java.lang.NullPointerException: Cannot read field "s" because "REPL.$JShell$12.a" is null
|        at (#3:1)

jshell> a.s = "toto";
|  Exception java.lang.NullPointerException: Cannot assign field "s" because "REPL.$JShell$12.a" is null
|        at (#4:1)

jshell> a = new A();
a ==> A@3f8f9dd6

jshell> a.s.toString()
|  Exception java.lang.NullPointerException: Cannot invoke "String.toString()" because "REPL.$JShell$12.a.s" is null
|        at (#6:1)

jshell>

More info in the JEP : https://openjdk.java.net/jeps/358

JEP 359: Records (Preview)

Stating the JEP : Records provides a compact syntax for declaring classes which are transparent holders for shallowly immutable data.

This is a new Java type (like class and enum), it’s purpose is to be a data carrier class. Records are implicitly final and cannot be abstract.

Records will provides default implementation for boilerplate code that you would otherwise have your IDE generated.

jshell> record Point(int x, int y) { }

jshell> Point p = new Point(); // all fields need to be initialized at construction time
|  Error:
|  constructor Point in record Point cannot be applied to given types;
|    required: int,int
|    found:    no arguments
|    reason: actual and formal argument lists differ in length
|  Point p = new Point();
|            ^---------^

jshell> Point p = new Point(1, 1);
p ==> Point[x=1, y=1]

All records have public accessors (but the fields are private) and a toString method

jshell> p.x //field is private
|  Error:
|  x has private access in Point
|  p.x
|  ^-^

jshell> p.x(); //public accessor
$8 ==> 1

jshell> p.toString(); //default toString()
$9 ==> "Point[x=1, y=1]"

All records have equals and hashCode methods whose implementations are based on the record type and state.

jshell> Point other = new Point(1,1);
other ==> Point[x=1, y=1]

jshell> other.equals(p);
$11 ==> true

jshell> other == p
$12 ==> false

You can override the default methods and constructor of a record.

When overriding the default constructor, there is no need to repeat the field initialization and you can directly access the fields of the record.

record Range(int lo, int hi) {
  public Range {
    if (lo > hi)  /* referring here to the implicit constructor parameters */
      throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
  }
}

More info in the JEP : https://openjdk.java.net/jeps/359

JEP 305: Pattern Matching for instanceof (Preview)

Every developer already wrote code that looks like this with the instanceof operator:

if (obj instanceof Integer) {
    int intValue = ((Integer) obj).intValue();
    // use intValue
}

The cast after the instanceof looks unnecessary as we just tested the type of the object.
This is where pattern matching comes into play, it will allow to verify that an object is on some type (like instanceof) and “extract” the ‘shape” of the object in a new variable.
Using pattern matching, we will be able to replace the previous code with this one

if (obj instanceof Integer intValue) {
    // use intValue
}

No more cast needed, and we assign the object in a local variable on the type validated by the instanceof.
To use pattern matching, we need to add the command line flag --enable-preview as this is a preview features.

Here is a more complete example via JSHell.

$ jshell --enable-preview
|  Welcome to JShell -- Version 14-ea
|  For an introduction type: /help intro

jshell> public void print(Object o) {
   ...> if(o instanceof String s) System.out.println("String =>" + s);
   ...> if(o instanceof Integer i) System.out.println("Integer =>" + i);
   ...> }
|  created method print(Object)

jshell> print(1)
Integer =>1

jshell> print("toto")
String =>toto

More info in the JEP: https://openjdk.java.net/jeps/305

Brian Goetz wrote an article that covers more Pattern Matching buildings and that prelude to what might happen in future versions of Java on the subject: https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html

Miscellaneous

  • JDK-8229485 : Add StrictMath::decrementExact, StrictMath::incrementExact and StrictMath::negateExact()
  • JDK-8232633 : Support for pluralization in CompactNumberFormat
  • JDK-8202385 : Add the annotation <code>@Serial</code> that allows to mark fields/methods as relative to serialization. As the serialization protocol is based on standards fields and methods and not something more robust, the compiler cannot validate the signature of those fields and methods. Now by annotating a field (like serialVersionUID) or a method (like writeObject) with @Serial the compiler will be able to verify the signature of those fields and mehtods and avoid potential development issue.

JEP 343: Packaging Tool (Incubator)

jpackage is a tool that allows to package your application in an OS native format (warning, this is the packaging format that is native not your application, this has nothing to do with GraalVM native image).

My OS is Ubuntu, so jpackage will package my application in the .deb format. I will take as example the getting-started application of Quarkus.

First, I need to use jpackage to generate a package. I need to give it the directory of my application (–input) and it’s main (here –main-jar to give jpackage the jar that contains the main).

$ jpackage --name getting-started --input target --main-jar getting-started-1.0-SNAPSHOT-runner.jar
WARNING: Using incubator modules: jdk.incubator.jpackage

Then I can use dpkg to install this package on my OS.

$ sudo dpkg -i getting-started_1.0-1_amd64.deb 
Sélection du paquet getting-started précédemment désélectionné.
(Lecture de la base de données... 256348 fichiers et répertoires déjà installés.)
Préparation du dépaquetage de getting-started_1.0-1_amd64.deb ...
Dépaquetage de getting-started (1.0-1) ...
Paramétrage de getting-started (1.0-1) ...

The application will be installed inside /opt/getting-started, to launch it there is an executable inside the bin directory.

$ /opt/getting-started/bin/getting-started 
2019-12-31 17:17:25,933 INFO  [io.quarkus] (main) getting-started 1.0-SNAPSHOT (running on Quarkus 1.0.1.Final) started in 1.130s. Listening on: http://0.0.0.0:8080
2019-12-31 17:17:25,937 INFO  [io.quarkus] (main) Profile prod activated. 
2019-12-31 17:17:25,937 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, resteasy]

My application files has been located inside /opt/getting-started/lib/app/, other directories contain some files related to jpackage.

Finally, I can uninstall the package via the dpkg command.

$ sudo dpkg -r getting-started
(Lecture de la base de données... 256753 fichiers et répertoires déjà installés.)
Suppression de getting-started (1.0-1) ...

More info in the JEP: https://openjdk.java.net/jeps/343

JEP 352: Non-Volatile Mapped Byte Buffers

The MappedByteBuffer API has been updated to support Non-Volatile Memory (NVM).

More info in the JEP: https://openjdk.java.net/jeps/352

JEP 370: Foreign-Memory Access API (Incubator)

A new incubator module has been created to holds an off-heap memory access API.

This functionality targets framework developers as it is rare to need to access native memory from inside a Java application.

Until now, framework developers mainly used ByteBuffer and Unsafe to access native memory. As the usage of Unsafe is strongly discouraged and ByteBuffer has a lot of disadvantage, this new API wants to be more easy to use and as performant as Unsafe (even more as it has been designed to be easily optimizable by the JIT).

Here is a simple usage of the API (it can cover more complex use cases with the concept of memory layout and stride to allow multi-dimensional layouts).

VarHandle intHandle = MemoryHandles.varHandle(int.class);

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   MemoryAddress base = segment.baseAddress();
   for (int i = 0 ; i &amp;lt; 25 ; i++) {
        intHandle.set(base.offset(i * 4), i);
   }
}

More info in the JEP: https://openjdk.java.net/jeps/370

Garbage Collector

A lot of news regarding GC: new functionalities, performance improvements, deprecation and deletion !

JEP 345: NUMA-Aware Memory Allocation for G1

G1 has been updated to be “NUMA-Aware”. NUMA – Non Uniform Memory Access -is a characteristic of modern CPU architectures; to make is very simple, the cost of cache / memory access can be different from one core to another to a cache line / a memory slot.

The G1 algorithm has been modified to take it into account (prefer to make an allocation closest to the CPU). ParallelGC is NUMA-aware since a long time, G1 is therefore just catching up on the subject.

This functionality mainly targets machines with several sockets or a large number of cores, some aggressive benchmarks on the GC side show a performance optimization of around 5% with the + UseNUMA option.

More info in the JEP : https://openjdk.java.net/jeps/345

JEP 363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector

After being deprecated with Java 9, CMS is finally deleted with Java 14, no one in the community wanted to maintain it.

CMS was a very powerful concurrent GC algorithm, targeting medium-sized heap, and highly configurable. It suffered from the lack of compaction phase, and Oracle wishing to invest more and more in G1 and ZGC, no longer wished to maintain another concurrent algorithm.

Until recently, it was one of the most efficient algorithms for heap of medium size, and very often we could not configure G1 to have such high performance. Hopefully with the new optimizations made in G1 in the latest versions of Java it can now manage to have performance close to CMS … or else there is always ZGC and Shenandoah ….

More info in the JEP : https://openjdk.java.net/jeps/363

JEP 365: ZGC on Windows et JEP 364: ZGC on macOS

The ZGC Garbage Collector has been ported to Windows (version 1803 of Windows 10 minimum) and macOS.

More info in the JEP: https://openjdk.java.net/jeps/364 et https://openjdk.java.net/jeps/365

Parallel GC

JDK-8204951 – Investigate to use WorkGang for Parallel GC : change in ParallelGC thread management, it now uses the same WorkGang as the other GCs. Less maintenance cost, and above all, optimized GC performance between 0% and 40% depending on the benchmark (average of 10%).

More info in the JBS : https://bugs.openjdk.java.net/browse/JDK-8204951

JDK-8220465 – Use shadow regions for faster ParallelGC full GCs : optimization of the implementation of full collections for ParallelGC. Tests have shown performance x2 to x3 in case of full GC!

More info in the JBS : https://bugs.openjdk.java.net/browse/JDK-8220465

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.