## I. System selection

About the most basic underlying image, usually most of us have only three choices: Alpine, Debian, CentOS; of these three for the operation and maintenance of the most familiar with the general CentOS, but unfortunately CentOS later no longer exists in a stable version, about its stability has been a mysterious problem; this is a matter of opinion, I Personally, I don’t use it if I can 😆.

Excluding CentOS, we’re only talking about Alpine or Debian; Alpine definitely wins from a mirror size point of view, but Alpine uses musl’s C library, which may have some compatibility issues with some deep dependencies on glibc. Of course, the depth of the dependency on glibc depends on the application, and I have only encountered a few font-related bugs with the OpneJDK in the official Alpine source so far.

On balance, my personal recommendation is that if the application has a deep dependency on glibc, for example containing some JNI-related code, then Debian or a Debian-based base image is a more stable choice; if there are no such heavy dependencies, then you can use Alpine when considering the size of the image. In fact, OpneJDK In fact, OpneJDK itself is not small, even if you use the Alpine version, after installing some common software, it will not be too small, so I personally used the Debian-based base image.

## II. JDK OR JRE

Most people don’t seem to distinguish between JDK and JRE, so to be sure you need to understand what JDK and JRE are:

• JDK: Java Development Kit
• JRE: Java Runtime Environment

JDK is a development kit, it contains some debugging-related tool chains, such as javac, jps, jstack, jmap and other commands, which are necessary for debugging and compiling Java programs, and JDK as a development kit includes JRE; while JRE is only a Java runtime environment, it only contains some commands and commands necessary for running Java programs. JRE is only a Java runtime environment, it only contains some commands and dependent libraries that are necessary for running Java programs, so JRE is smaller and lighter than JDK.

If you only need to run Java programs such as a jar package, then JRE is sufficient; but if you want to capture some information at runtime for debugging, then you should choose JDK. **My personal habit is to use JDK as the base image to solve some production problems, and avoid the need to mount the JDK toolchain for debugging in some special cases. Of course, if there is no such need, and the size of the image is sensitive, then you can consider using JRE as the base image. **

## Three, JDK selection

### 3.1, OracleJDK or OpenJDK

The choice between these two depends on one of the most direct questions: whether the application code uses Oracle JDK private APIs.

Usually “using these private APIs” means introducing some relevant classes, interfaces, etc. under the com.sun.* package, many of these APIs are private to Oracle JDK and may not be included at all in OpneJDK or have been changed. So if the code contains relevant calls then only Oracle JDK can be used.

It’s worth clarifying that in many cases the use of these APIs is not really a business requirement, it’s likely that the developer “slipped” when importing the package and it just so happens that the imported Classes and so on can also implement the corresponding functionality; for such imports can be replaced smoothly, for example with Apache Commons-related implementations . There is also a case where the developer found the import by mistake, but did not format the code and clean up the package, this will leave the relevant import reference in the code header, and Java allows such useless import; for this case, just reformat and optimize the import.

Tips: IDEA uses Option + Command + L (for formatting) and Control + Option + O (for automatic package import optimization).Tips.

### 3.2, OracleJDK rebuild problem

When there is no way to have to use Oracle JDK, it is recommended to download Oracle JDK package and write Dockerfile to create the base image. But this involves a core problem: Oracle JDK does not provide historical versions, so if you want to consider future rebuilding problems, it is recommended to keep the downloaded Oralce JDK package.

### 3.3, OpenJDK distribution

As we all know, OpenJDK is an open source distribution, based on open source protocols major vendors are providing some value-added services, but also pre-compiled some Docker images for our use; currently some of the mainstream distribution versions are as follows:

• Amazon Corretto
• IBM Semeru Runtime
• Azul Zulu
• Liberica JDK

Some distributions may offer a wider choice of base images, for example, AdoptOpenJDK offers three base image distributions based on Alpine, Ubuntu, and CentOS; others offer other JVM implementations, for example, IBM Semeru Runtime offers a pre-compiled version of the OpenJ9 JVM. for example, IBM Semeru Runtime offers a pre-compiled version of the OpenJ9 JVM.

I personally like AdoptOpenJDK because it is community-driven, composed of JUG members and some vendors and other community members; Amazon Corretto and IBM Semeru Runtime are high end cloud players by name, and the usability is better. Others like Azul Zulu and Liberica JDK are JVM vendors, some of which are not recommended as they have a bit of black stuff.

AdoptOpenJDK has now been merged into Eclipse Foundation and is now called Eclipse Adoptium; so if you want to use the AdoptOpenJDK image, you should use eclipse-temurin in Docker Hub .com/_/eclipse-temurin) user.

## IV. JVM Selection

For JVM implementation, Oracle has a JVM implementation specification that defines what features the Java-compatible code should have when running this VM; so ** as long as this JVM implementation specification is met and certified, then this JVM implementation can theoretically be used in production. ** There are many JVM implementations on the market today:

• Hotspot
• OpenJ9
• TaobaoVM
• LiquidVM
• Azul Zing

These JVM implementations may have different features and performance, for example Hotspot is the most commonly used JVM implementation with the best overall performance, compatibility, etc.; OpneJ9, created by IBM and currently part of the Eclipse Foundation, is more containerization friendly, offering faster startup and memory footprint features.

It is generally recommended to use the “standard” Hotspot if you are not very familiar with the JVM; if you have higher requirements and expect to debug some JVM optimization parameters yourself, please consider Eclipse OpenJ9. I personally prefer OpenJ9, because its documentation is very well written and can be read with care. If you want to use the OpenJ9 image, it is recommended to use the ibm-semeru-runtimes pre-compiled image directly.

## V. Semaphore Passing

When we need to close a program, usually the system will send a termination signal to the process, similarly when the container stops Kubernetes or other container tools will send a termination signal to the process with PID 1 in the container; if the container is running a Java program, then the signal is passed to the JVM and Java related frameworks such as Spring Boot will detect the signal and start executing something. If a Java program is running inside the container, the signal is passed to the JVM and Java-related frameworks such as Spring Boot detect the signal and start performing some cleanup before shutdown, which is called a “graceful shutdown “.

If we don’t get the signal to the JVM correctly when containerizing a Java application, then a scheduler like Kubernetes will force a shutdown after waiting for the container shutdown to time out, ** which is likely to result in some Java programs not releasing resources properly, such as database connections not closing, registries not back registering, etc. ** To verify this, I created a Spring Boot sample project to test it, which contains the following core files (see GitHub for the full code):

• BeanTest.java: Use @PreDestroy to register a Hook to listen for shutdown events to simulate a graceful shutdown
• Dockerfie.bad: Dockerfile for error demonstration
• Dockerfile.direct: Run command directly to achieve graceful shutdown
• Dockerfile.exec: Use exec to achieve graceful shutdown
• Dockerfile.bash-c: Use bash -c for graceful shutdown
• Dockerfile.tini: Verify that tini does not shut down gracefully in some cases
• Dockerfile.dumb-init: Verify that dumb-init does not shut down gracefully in some cases

Since the BeanTest print-only tests are generic, here is the code:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  package com.example.springbootgracefulshutdownexample; import org.springframework.stereotype; import javax.annotation.PreDestroy; @Component public class BeanTest { @PreDestroy public void destroy() { System.out.println("=================================="); System.out.println("Received termination signal, executing graceful closure...") ; System.out.println("=================================="); } } 

### 5.1. Wrong signaling

In many primitive Java projects there is usually a startup script, which may be self-written, or it may be some old Tomcat startup script, etc.; when we use the script to start and do not adjust the Dockerfile properly, there will be a problem with the signal not being passed correctly; for example, the following error example:

 1 2 3  #! /usr/bin/env bash java -jar /SpringBootGracefulShutdownExample-0.0.1-SNAPSHOT.jar 

Dockerfie.bad: using bash startup script, which causes the termination signal not to be delivered

 1 2 3 4 5 6 7 8 9  FROM eclipse-temurin:11-jdk COPY entrypoint.bad.sh / COPY target/SpringBootGracefulShutdownExample-0.0.1-SNAPSHOT.jar / # The following methods fail to forward signals #CMD /entrypoint.bad.sh # CMD ["/entrypoint.bad.sh"] CMD ["bash", "/entrypoint.bad.sh"] 

After running with this Dockerfile package, ** it is obvious that when using the docker stop command, it stalls for a while (actually docker is waiting for the processes in the container to exit by themselves), and when the scheduled timeout is reached, the processes in the container are forcibly terminated, so no log of graceful shutdown is printed:**

### 5.2. Proper signaling

#### 5.2.1, direct run method

There are many ways to solve this signaling problem; for example, it is common to run a java program directly using the CMD or ENTRYPOINT commands:

Dockerfile.direct: run the java program directly, it can receive the termination signal normally

 1 2 3 4 5  FROM eclipse-temurin:11-jdk COPY target/SpringBootGracefulShutdownExample-0.0.1-SNAPSHOT.jar / CMD ["java", "-jar", "/SpringBootGracefulShutdownExample-0.0.1-SNAPSHOT.jar"] 

As you can see, running the java command directly in the Dockerfile allows jvm to properly notify the application of a graceful shutdown:

#### 5.2.2. Indirect Exec method

If you are familiar with Docker, you should know that running commands directly in Dockerfile cannot resolve environment variables; however, sometimes we rely on scripts to resolve variables, so we can first resolve them in scripts and use exec for final execution; this way we can also ensure the signaling (not pictured):

entrypoint.exec.sh: exec executes the final command, and can forward the signal

### 7.1. Default DNS caching

The default DNS cache results without any settings are as follows (just run the script directly):

As you can see, the DNS TTL is set to 30s by default, but if Security Manager is enabled it becomes -1s, so what does -1s mean (taken from the OpenJDK 11 source code):

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  /* The Java-level namelookup cache policy for successful lookups: * * -1: caching forever * any positive value: the number of seconds to cache an address for * default value is forever (F) * default value is forever (FOREVER), as we let the platform do the * For security reasons, this caching is made forever when For security reasons, this caching is made forever when * a security manager is set. */For security reasons, this caching is made forever when * a security manager is set. private static volatile int cachePolicy = FOREVER; /* The Java-level namelookup cache policy for negative lookups: * -1: caching forever. * -1: caching forever * any positive value: the number of seconds to cache an address for * default value is 0. * It can be set to some other value for * performance reasons. */It can be set to some other value for * performance reasons. private static volatile int negativeCachePolicy = NEVER; 

### 7.2. Setting up DNS caching

To avoid this weird DNS caching policy issue, it is best to manually set the DNS cache time at startup by adding the -Dsun.net.inetaddr.ttl=xxx parameter:

** As you can see, once we set the DNS cache manually, then Security Manager will follow our settings regardless of whether it is turned on or not. ** For more detailed debugging of DNS caching, we recommend using Alibaba’s open source DCM tool.

## VIII. Native Compilation

Native compilation optimization means that Java code is compiled by GraalVM into binaries that can be executed directly by the platform, and the compiled executable will run much faster. **But GraalVM requires code layer adjustment, framework upgrade, and other operations, which are generally demanding; however, if you are working on a new project, it is best to have the development support for Native compilation with GraalVM, which can be a huge boost to startup speed. **

The project described above for testing graceful closure already has built-in support for GraalVM, just download GraalVM and set the JAVA_HOME and PATH variables, and use mvn clean package -Dmaven.test.skip=true -Pnative to compile:

After successful compilation, a binary file will be created in the target` directory that can be executed directly, and the following is a comparison of the startup speed:

However, in general, this approach is not yet particularly mature, and the domestic Java ecosystem is still dominated by OpneJDK 8, so old projects need to be adjusted to meet GraalVM; so the conclusion is that new projects should be supported as much as possible, and old projects should not die. projects should not die.So the conclusion is that new projects should be supported as much as possible, and old projects should not die.