OpenJDK 9 and 10 — Key Features for Cloud Environments
Learn about JDK 9 and JDK 10 in this article by Luigi Fugaro, an architect in the EMEA Middleware team who has been developing all kinds of web applications, dealing with backend and frontend frameworks.
The new era of information technology has gone through a constant but gradual evolution of languages, frameworks, and architectures. The great competition that drives the global market has given rise to the need for a complete review of development platforms and their updating and evolution processes.
From an application development perspective, open sourcing has provided a great contribution over the years. One of the most popular programming languages that have grown, thanks to the help of open source communities, is Java.
Java, released by Sun Microsystems, was created with the concept of write once run anywhere. The promise of great portability of software, ease of learning, and use from the developers have facilitated the rapid spread of Java in the information technology ecosystem.
The work Sun did during these 10 years, from the first public release of 1996 to the birth of OpenJDK and the open source release of the JVM core and JSE platform in 2007, ideally made its role with regard to Java seem like that of an evangelist.
Java has since then evolved through different stakeholders such as Oracle Corporation and Eclipse Foundation. Java SE 9 and 10 are the latest OpenJDK versions with promising features and improvements.
Java SE 9 is the revolutionary element entailing the modularization of the JDK reached through the Jigsaw project.
The main goals of JDK 9 were to make Java SE more flexible and scalable, improve security and maintenance, make it easier to construct, maintain, deploy, and upgrade large applications, and to enable improved performance.
Previously, the community had two main criticisms of the Java SE platform:
· The slowness in the release of the newer versions
· The size of the JDK both in terms of space occupation and class size (approximately 4,240 classes in JDK 8)
It was difficult to find a solution that would allow overcoming these limits without abandoning the backward compatibility between versions.
Therefore, it became difficult to eliminate obsolete classes or classes designed for internal use but used intensively, especially by the framework, via Java reflection.
Through the Jigsaw project, it became possible to modularize the JDK in its core code and provide a tool for the realization of applications able to significantly decouple the interfaces of exposure of its services with respect to the actual implementation.
For JDK core code, it is possible to encapsulate JDK APIs, as described in JEP 260, using the following approach:
- Encapsulate, by default, all internal APIs that are considered non-critical
- Encapsulate all internal APIs, that are considered critical, for which exist, in JDK 8, supported replacements
- Do not encapsulate critical internal APIs, but implement the following steps:
- Deprecate them in JDK 9
- Define a plan to remove these APIs in JDK 10
- Implement a workaround solution via a command-line parameter
- Remove, from the JDK distribution, a limited number of supported Java Community Process (JCP) standard APIs
- Remove the extension mechanisms and the endorsed standards override
- Java EE modules, due to the Jigsaw project, are not resolved by default
This way helps you to easily obtain a small bootable Java runtime that contains only the features, in terms of classes and interfaces that you really need. This avoids the presence of useless code that can have only negative side effects in terms of footprint and space allocation.
For example, you could easily analyze this using the following command:
$ java -listmods
As mentioned before, both the JDK and the application can benefit from modular development.
Decoupling the components present in the applications is essential in microservice architectures, which need a very agile software life cycle to reduce time to market. Using a modularity approach, you could easily achieve the following:
- Loose coupling between components
- Clear contracts and dependencies between components
- Hidden implementation using strong encapsulation
The main element in your implementation is the module. Developers can organize their code into a modular structure, within which are declared dependencies inside their respective module definition files.
The properties of a module are defined as a file named module-info.java that contains the following attributes:
- The module name
- The module’s packages that you want to make available publicly
- The dependencies, direct or transitive, that the module depends on
- The list of the services that the module consumes
- All possible implementation of the service that the module provides
The following are the main keywords used to set the main features of a module through the
module: The module definition file starts with this keyword followed by its name and definition.
provides ... with ...: The
providesthe keyword is used to indicate that the module provides implementations for a defined service interface. The service interface is expressed using the
withkeyword. See Java 9 Modularity: First Look
requires: This keyword is used to indicate the dependencies of the modules. A module name has to be specified after this keyword and the list of dependencies are set through multiple required directives.
transitive: This keyword is set after the
requireskeyword; with this feature, you are declaring that any module that depends on the module defining
requires transitive <modulename>gets an implicit dependence on the
uses: This keyword is used to indicate the service interface that this module is using; a type name, complete with fully qualified class or interface name, has to be specified after this keyword.
opens: This keyword is used to indicate the packages that are accessible only at runtime; you can also use them for introspection, using Reflection APIs. This is quite important for libraries and frameworks that use reflection APIs in order to be as abstract as possible; the
opensdirective can also be set at module level—in this case, all packages of the module are accessible at runtime.
exports: This keyword is used to indicate the packages of the module that are publicly available; a package name has to be specified after this keyword. You can further see Java 9 Modularity: First Look By Sander Mak to learn more about Module
Java Jigsaw versus OSGi
However, the two approaches, Java Jigsaw module and Open Service Gateway Initiative (OSGi), have some differences:
- OSGi’s adoption is largely due to its support for dynamic component control. In this case, plugins or components are loaded dynamically and then activated, deactivated, and even updated or removed as needed. Presently, this dynamic module life cycle is not available with Java modules.
- Compared to Java modules, OSGi supports improved versioning. Other OSGi advantages are related to isolation; for example, bundle changes require only the direct dependencies to be recompiled, whereas a Java module’s entire layer, along with all child layers, need to be recompiled if just one module changes.
- The downside is that OSGi bundles still suffer from classpath issues, such as runtime exceptions for missing dependencies, or arbitrary class loading for packages with the same name.
- Additionally, OSGi requires a class loader per module, which can affect some libraries that expect only a single class loader. Java modules don’t allow split packages which are considered a big improvement in Java overall and don’t have similar class loader requirements or restrictions. One big advantage Java modules have over OSGi is compiler support.
We can get the most out of the modularization of the application components in microservice architecture, combining the best of both technologies.
The overall strategy is to use Java modules to modularize libraries (either imported or exported) and the JVM itself and use OSGi on top to handle application modularity and dynamic life cycle control. See What’s New in Java 9 — Modules and more to learn more about it.
JDK 10 introduced some new features related to cloud environments and microservice architecture. The first one is Docker awareness, which is supported for Linux only. With this feature, you can extract container-specific information about the number of CPUs (automatically) and allocated memory (automatically).
The following new JVM configuration parameters have been introduced:
-XX:UseContainerSupport: The JVM has been updated in order to be aware that it is running in a Docker container. In this way, it will extract container specific configuration information and it will not query the operating system. The more important information that it will be extracted is the total memory that has been allocated to the container and the number of CPUs. The value of CPUs available to the Java process is calculated from any specified CPU shares, CPU quotas or CPU sets.
-XX:ActiveProcessorCount: This value overrides any other logic of CPU detection implemented automatically by the JVM.
-XX:MinRAMPercentage: This parameter allows users that run the JVM into Docker containers to have more control over the amount of system memory that will be used for the Java Heap allocation.
A great performance improvement is achieved with the Parallel Full GC for G1, as described in JEP 307. With this feature, full GC occurs on parallel threads with great benefits, such as the following:
- Low latency
- High throughput
- No/fewer stop-of-world (STW) pauses
- Improved G1 worst-case latencies
In order to obtain better performance, it has also introduced the application class-data sharing (CDS) with JEP 310. This feature reduces resource footprint when multiple JVMs are running on the same physical machine and improves the startup time of applications.
Furthermore, processes and applications can share common class metadata (class data), from a shared archive (the CDS archive); prior to Java SE 10, the use of CDS had been restricted to the bootstrap class loader only.
Another important element in an environment such as the cloud that must be as responsive as possible is the Thread-Local Handshakes feature, defined in JEP 312. It’s a new way to execute a
callback on threads that reduces the impact of acquiring a stack trace sample, for example, for profiling operations.
It makes it possible to cheap-stop individual threads, reduce stops of STW pauses, and give better GC performance. With the Thread-Local Handshakes feature, it is possible to stop single threads and not just all threads or none; in this way, you don't need to make a global JVM safe point, giving a great performance improvement.
Last but not least, JDK 10 makes it possible to perform heap allocation on alternative devices, as described in JEP 316. See What’s New in Java 10 to learn more about Java 10 features.
This great feature, realized with the contribution of Intel, allows the JVM to allocate the heap needed to store Java objects on a different memory device specified by the user, for example, a Non-Volatile Dual In-line Memory Module (NVDIMM).
This aspect could be extremely important in a multi-JVM environment, where you will instruct the processes with lower priority to use the NVDIMM memory while instructing the higher priority processes to use dynamic random access memory (DRAM).
The features described here are only a small set of the new features released in JDK 9 and 10 that demonstrate the great effort done to improve the Java language and make it a good choice for microservice implementation in cloud environments.
In September 2018, JDK 11 was released with some new great features, such as Epsilon — a no-op garbage collector. Do you still have doubts about the use of Java for new microservice architectures?
If you can further explore Hands-On Cloud-Native Microservices with Jakarta EE to discover how cloud-native microservice architecture helps you to build dynamically scalable applications by using the most widely used and adopted runtime environments.
Hands-On Cloud-Native Microservices with Jakarta EE will help you be equipped with the skills you need to build highly resilient applications using cloud-native microservice architecture.
Thanks, You made it to the end of the article … Good luck with your Cloud computing with Java! It’s certainly not going to be easy, but by using these features and guides would make it little less painful
If you like this article, then please share with your friends and colleagues, and don’t forget to follow javareivisted on Twitter! and @Javarevisited on Medium