The question is not if you should move to Java 11 or a later version, but when. Within
the next few years, Java 8 will no longer be supported, and users will
have to move to Java 11 or later. We argue that there are benefits to moving to
Java 11 and encourage teams to do so as soon as possible.
Since Java 8, new features have been added and enhancements have been
made. There are noticeable additions and modifications to API, and there
are enhancements that improve startup, performance, and memory usage.
Transitioning to Java 11
Transitioning to Java 11 can be done in a stepwise fashion. It is not
required for code to use Java modules to run on Java 11. Java 11 can be
used to run code developed and built with JDK 8.
But there are some potential issues, primarily concerning deprecated
API, class loaders, and reflection.
This section does not enumerate all the changes made in Java versions 9 [1],
10 [2], and 11 [3]. Changes that have an impact on
performance, diagnostics, and productivity are highlighted.
Modules address issues of configuration and encapsulation that are
difficult to manage in large-scale applications running on the
classpath. A module is a self-describing collection of Java
classes and interfaces, and related resources.
Modules make it possible to customize runtime configurations that
contain only the components required by an application. This customization creates a
smaller footprint and allows an application to be statically linked, using
jlink,
into a custom runtime for deployment. This smaller footprint can be particularly useful
in a microservices architecture.
Internally, the JVM is able to take advantage of modules in a way that
makes class-loading more efficient. The result is a runtime that is
smaller, lighter, and faster to start. Optimization techniques used by
the JVM to improve application performance can be more effective
because modules encode which components a class requires.
For programmers, modules help enforce strong encapsulation by
requiring explicit declaration of which packages a module exports and
which components it requires, and by restricting reflective access.
This level of encapsulation makes an application more secure and
easier to maintain.
An application can continue to use the classpath and does not have
to transition to modules as a requisite for running on Java 11.
Java Flight Recorder (JFR) gathers diagnostic and profiling data from
a running Java application. JFR has little impact on a running Java
application. The collected data can then be analyzed with Java
Mission Control (JMC) and other tools. Whereas JFR and JMC were
commercial features in Java 8, both are open source in Java 11.
Java Mission Control (JMC) provides a graphical display of data
collected by the Java Flight Recorder (JFR) and is open source in Java
11. In addition to general information about the running application,
JMC allows the user to drill down into the data. JFR and JMC can
be used to diagnose runtime issues such as memory leaks, GC overhead,
hot methods, thread bottlenecks, and blocking I/O.
Java 11 has a common logging system for all components of the JVM.
This unified logging system allows the user to define what components
to log, and to what level. This fine-grained logging is useful for
performing root-cause analysis on JVM crashes and for diagnosing
performance issues in a production environment.
New API has been added to the Java Virtual Machine Tool Interface
(JVMTI) for sampling Java heap allocations. The sampling has
low-overhead and can be enabled continuously. While heap allocation
can be monitored with Java Flight Recorder (JFR), the sampling method
in JFR only works on allocations. The JFR implementation may also miss
allocations. In contrast, heap sampling in Java 11 can provide
information about both live and dead objects.
Application Performance Monitoring (APM) vendors are starting to
utilize this new feature and the Java Engineering Group is investigating
its potential use with Azure performance monitoring tools.
Getting a snapshot of the stack for the current thread is often used
when logging. The problem is how much of the stack trace to log, and
whether to log the stack trace at all. For example, one may want to see
the stack trace only for a certain exception from a method. The
StackWalker class (added in Java 9) gives a snapshot of the stack and
provides methods that give the programmer fine-grained control over
how to consume the stack trace.
The following garbage collectors are available in Java 11: Serial,
Parallel, Garbage-First, and Epsilon. The default garbage collector in
Java 11 is the Garbage First Garbage Collector (G1GC).
Three other collectors are mentioned here for completeness. The Z
Garbage Collector (ZGC) is a concurrent, low-latency collector that
attempts to keep pause times under 10ms. ZGC is available as an
experimental feature in Java 11. The Shenandoah collector is a
low-pause collector that reduces GC pause times by performing more
garbage collection concurrently with the running Java program.
Shenandoah is an experimental feature in Java 12, but there are
backports to Java 11. The Concurrent Mark and Sweep collector (CMS) is
available but has been deprecated since Java 9.
The JVM sets GC defaults for the average use-case. Often,
these defaults, and other GC settings, need to be tuned for optimum
throughput or latency, according to the application's requirements.
Properly tuning the GC requires deep knowledge of the GC, expertise
that the Microsoft Java Engineering Group
provides.
G1GC
The default garbage collector in Java 11 is the G1 garbage collector
(G1GC). The aim of G1GC is to strike a balance between latency and
throughput. The G1 garbage collector attempts to achieve high
throughput by meeting pause time goals with high probability. G1GC is
designed to avoid full collections, but when the concurrent
collections can't reclaim memory fast enough a fallback full GC will
occur. The full GC uses the same number of parallel worker threads as
the young and mixed collections.
Parallel GC
The parallel collector is the default collector in Java 8. Parallel GC is a
throughput collector that uses multiple threads to speed up garbage
collection.
The Epsilon garbage collector handles allocations but does not reclaim
any memory. When the heap is exhausted, the JVM will shut down.
Epsilon is useful for short-lived services and for applications that
are known to be garbage-free.
Prior to Java 10, memory and CPU constraints set on a container were
not recognized by the JVM. In Java 8, for example, the JVM will default the maximum
heap size to ¼ of the physical memory of the underlying host. Starting with
Java 10, the JVM uses constraints set by container control groups
(cgroups) to set memory and CPU limits (see note below).
For example, the default maximum heap size is ¼ of the container's memory limit
(e.g., 500MB for -m2G).
JVM Options were also added to give Docker container users
fine-grained control over the amount of system memory that will be
used for the Java heap.
This support is enabled by default and is only available on
Linux-based platforms.
Note
Most of the cgroup enablement work was backported to Java 8 as of
jdk8u191. Further improvements may not necessarily be backported to 8.
It is possible in Java 11 to create a jar file that contains multiple,
Java-release-specific versions of class files. Multi-release jar files make it possible
for library developers to support multiple versions of Java without
having to ship multiple versions of jar files. For the consumer of
these libraries, multi-release jar files solves the issue of having to
match specific jar files to specific runtime targets.
Miscellaneous performance improvements
The following changes to the JVM have a direct impact on performance.
JEP 197: Segmented Code Cache [14] - Divides the code cache
into distinct segments. This segmentation provides better control of
the JVM memory footprint, shortens scanning time of compiled
methods, significantly decreases the fragmentation of code cache,
and improves performance.
JEP 254: Compact Strings [15] - Changes the internal
representation of a String from a two bytes per char to one or two
bytes per char, depending on the char encoding. Since most Strings
contain ISO-8859-1/Latin-1 characters, this change effectively
halves the amount of space required to store a String.
JEP 310: Application Class-Data Sharing [16] - Class-Data
Sharing decreases startup time by allowing archived classes to be
memory-mapped at runtime. Application Class-Data Sharing extends
class-data sharing by allowing application classes to be placed in the CDS
archive. When multiple JVMs share the same archive file, memory is
saved, and the overall system response time improves.
JEP 312: Thread-Local Handshakes [17] - Makes it possible to
execute a callback on threads without performing a global VM
safepoint, which helps the VM achieve lower latency by reducing the
number of global safepoints.
Lazy Allocation of Compiler Threads [18] - In tiered
compilation mode, the VM starts a large number of compiler threads.
This mode is the default on systems with many CPUs. These threads
are created regardless of the available memory or the number of
compilation requests. Threads
consume memory even when they are idle (which is almost
all the time), which leads to an inefficient use of resources. To
address this issue, the implementation has been changed to start
only one compiler thread of each type during startup. Starting
additional threads, and shutting down unused threads, is handled
dynamically.
The following changes to the core libraries have an impact on
performance of new or modified code.
JEP 193: Variable Handles [19] - Defines a standard means to
invoke the equivalents of various java.util.concurrent.atomic and
sun.misc.Unsafe operations upon object fields and array elements, a
standard set of fence operations for fine-grained control of memory
ordering, and a standard reachability-fence operation to ensure that
a referenced object remains strongly reachable.
JEP 269: Convenience Factory Methods for Collections [20] -
Defines library APIs to make it convenient to create instances of
collections and maps with small numbers of elements. The static
factory methods on the collection interfaces that create compact,
unmodifiable collection instances. These instances are inherently
more efficient. The APIs create collections that are
compactly represented and do not have a wrapper class.
JEP 285: Spin-Wait Hints [21] - Provides API that allows Java
to hint to the run-time system that it is in a spin loop. Certain
hardware platforms benefit from software indication that a thread is
in a busy-wait state.
JEP 321: HTTP Client (Standard) [22]- Provides a new HTTP
client API that implements HTTP/2 and WebSocket and can replace the
legacy HttpURLConnection API.
Start here and learn how you can build, migrate and scale Java applications on Azure using Azure services. Use tools and frameworks that you know and love – Spring, Tomcat, WildFly, JBoss, WebLogic, WebSphere, Maven, Gradle, IntelliJ, Eclipse, Jenkins, Terraform and more.