Back in 1995, a team of engineers from Sun Microsystems was trying to come up with a name for their new software platform and programming language. In a brainstorming session that’s still somewhat shrouded in mystery, they chose the name Java – a nickname for coffee (hence the logo) since, in its early days, coffee mostly originated from the Indonesian island of Java.
Fast forward to today and, just like coffee, Java now comes in many flavors.
There are many JDK distributions to choose from, and new Java versions released much faster than ever before. There are also JVM and garbage collector optimizations, frameworks that simplify the development of Java-based microservices, and an overall drive to make Java competitive in cloud-native environments.
As 2020 marks Java’s 25th birthday, the Java landscape is more diverse and dynamic than ever.
Is Java Still Free?
This is an interesting question that everyone is asking.
Oracle tried to settle this issue once and for all at the Jax London Conference for Java & Software Innovation 2019, which included over 60 sessions, hands-on workshops, and insightful keynotes. It was quite exciting to be in the presence of so many speakers, thinkers, and innovators from the world’s leading tech teams.
Discover some key takeaways from the conference that will bring you up-to-date with what’s happening at the core of the Java platform.
Oracle’s announcement in 2018 that public updates for the distributions of Java 8 and 11 will no longer be free of charge caused concern and confusion in the tech community. The fact that Java was free for commercial use gave it an edge over its competitors, and this was suddenly gone.
Or wasn’t it?
Java community leaders quickly responded with an open letter (reassuringly titled Java is still free), in which they explained and advocated for the use of production-ready, free alternatives to the Oracle build.
But how does this type of build happen?
Primarily, it must implement the Java SE specification, generally building upon the OpenJDK source code. Additionally, to be considered a Java SE-compatible implementation, it must pass the TCK (Technology Compatibility Kit) test suite.
For the record, OpenJDK is the free, open-source, reference implementation of Java SE, collectively developed and maintained by Oracle, the OpenJDK and Java communities, Red Hat, Azul Systems, IBM, Apple, SAP, etc. Developers release it under the GNU General Public License (GNU GPL) v2. All of the major JDK distributions out there (including Oracle’s own Java SE) are basically derived from OpenJDK.
Which Java Versions and Distributions Should You Use?
First of all, be aware that there are underlying costs if you decide to use the latest Java versions (11+) of Oracle Java SE in production. To be fair, Oracle justifies its new license model by heavily investing in developing and patching Java. They also still provide their own free-to-use OpenJDK distribution, which has only some minor differences from the paid version, while both have the same applied security patches.
The good news is, there are plenty of alternatives to choose from.
As recommended by the community, a reliable choice is AdoptOpenJDK – a distribution developed in part by the community, free for commercial use, and intended to offer security patches until at least 2022. Due to legal issues with Oracle, it does not run the TCK suite but is thoroughly tested with a suite of functional, integration, security, and performance tests.
Likewise, if production is deployed on RedHat or AWS, their distributions (RedHat / Amazon Corretto) are the natural choice since they’re optimized for the environment in which they are running.
Here are some of the details of the most important Java distributions:
|Build||TCK||License||Free Public Updates||Commercial Support||LTS|
|AdoptOpenJDK||No||GPLv2 (free to use commercially)||Until at least Sep 2023||IBM, jClarity (acquired by Microsoft in 2019)||Yes|
|Amazon Corretto||Yes||GPLv2||Until at least June 2023||No||Yes|
|Liberica JDK||Yes||GPLv2||Until at least 2023||BellSoft||Yes|
|Oracle Java SE||Yes||Oracle Binary Code License Agreement (paid license)||No||Oracle||Yes|
|Oracle OpenJDK||Yes||GPLv2||Last update in March 2019||No||No|
“It runs on my machine
Then we’ll ship your machine
.. and that’s how Docker was born”
Though obviously not true, the joke above captures the idea that containers promise to run anywhere. This is the same principle to which Java also adheres (the slogan “write once, run anywhere” comes to mind) through the design of the JVM. The question is: How well can Java and containers run together anywhere?
- What exactly are containers?
Although they’ve been around for almost 20 years, containers have revolutionized the way applications are designed, developed, and deployed over the past few years.
From the outside, containers appear to be very similar to virtual machines: they’re isolated, own processes and network interfaces, allow the installation of packages, can be packaged as images, and can be accessed through SSH, etc.
However, their underlying infrastructure is quite different: containers running in a container environment share the same Linux host operating system, while a VM system may run multiple operating systems.
- Container internals
On a deeper level, container infrastructure is actually built upon the Linux kernel features of namespaces and cgroups. The namespaces can provide isolated views of the host system, which translates to the fact that containers can have their own view on processes. Furthermore, cgroups allow the limitation on resources, which means that the containers can be run with hard limits on memory or CPU. This is the point where containers and the JVM may come into conflict.
- Memory management
The JVM has its own memory model, and by default, will allocate as maximum heap size ¼ of the available physical memory. It’s important to note that up until Java version 8, the Java runtime is container unaware, so even if it runs inside a container, it still views the host’s memory as the available memory.
On the other hand, a Docker container started with a hard limit on memory will kill a process that tries to allocate more memory than that hard limit. Therefore, trying to run a Java application (pre-Java version 9) in a container with a hard limit on memory (with swap disabled) that is lower than ¼ of the host’s physical memory will fail.
- Bringing Docker and the JVM on the same page
The solution is to either to limit the maximum heap size through the Xmx parameter passed to the JVM through JAVA_OPTIONS, or to use a base Docker image like the one provided Fabric8, which knows how to dynamically adapt to the container restrictions when starting the Java process.
Java version 9 partially solved this problem with some experimental VM options that can be passed when running the container, while Java version 10 brought all the improvements needed to make the JVM container aware, making the allocation of the heap size relative to the memory limit set on the container.
Another critical aspect of running applications in containers is the images from which the container instances are started. A general rule about images: It’s best to keep them as small as possible, which will result in faster updates, lower network costs, and fewer components prone to security attacks.
You can reduce the image size by basing the image on lightweight Linux distributions such as Alpine, which provides a minimum set of minimized versions of standard UNIX utilities.
A C library installation is mandatory to ensure that Java runs on top of Alpine (or any other distribution) since part of the JDK is written in C/C++.
Usually, this library is the standard glibc implementation, so common Alpine Java images (like the one provided by AdoptOpenJDK, for example) just install the glibc package on top of Alpine. However, as Alpine itself is built upon a different library – musl libc – the image size could be furthered reduced if the JDK is only compiled with this library. This is where OpenJDK’s Portola Project comes into play (as an experimental feature in Java version 13), whose goal is to port the JVM to musl libc.
Keep your microservices light. Reducing the image size can, of course, also be achieved with optimizations made on the application’s side. In Java, in particular, it can be accomplished by reducing the number of dependencies or by using the modules feature.
Containers for Integration Testing
Another topic in the context of containers is the use of containers for integration testing. Why do I mention this? Since integration tests can get pretty challenging to write and maintain in complex environments, containers can come to the rescue by providing isolated, lightweight, throwaway, and easy-to-run instances of the components that make up the testing environment – like databases, brokers, web servers, etc.
In Java, this is possible through the TestContainers library, which supports JUnit tests that bring up and automatically tear down Docker containers required during the integration test flow.
Java in the Cloud and Beyond
Cloud-native applications are gradually becoming the industry standard. Serverless architectures are also gaining popularity and support from cloud providers.
Like any other language or framework, Java needs to adapt to these trends. Several recent developments in the Java landscape were triggered by the need to make Java efficient in the cloud environment.
When it comes to running in serverless mode, a particular shortcoming of Java is the cold start. Oracle has addressed this with techniques like modularization, class-data sharing, ahead-of-time compilation, and, most promising, the use of the GraalVM runtime instead of OpenJDK.
Also, memory efficiency is paramount in the cloud, as the cost is generated by memory usage. This has, in turn, prompted the community to come up with optimizations and alternatives for garbage collectors.
Java is Here to Stay!
Challenges aside, Java is definitely a main actor on the cloud stage. The major cloud providers like AWS, Google Cloud, or Oracle Cloud are building part of their own services with Java, offering wide support for Java on their platforms.
As Java – still at the top of the Tiobe Index – prepares for the new decade with exciting new features, we can say that it has come a long way to reach the pinnacle of next-generation software development. No matter what trends emerge, developers and businesses will look to Java as a platform that can create innovative products.