Java framework born of "cloud": building native executables

Keywords: Java Programmer architecture

Today, let's take a look at how much faster the native executable built by Quarkus is than the Spring application,   Ecological maturity is not discussed here  .

TLDR

Let's draw a conclusion and make a comparison with the Spring Web application with only one Controller.

Application start time: 0.012s vs 2.294s

Image size: 49MB vs 237 MB

Spring application image usage   openjdk:11.0-jre-slim   As a base image, the size is 220MB.

docker images
REPOSITORY                                    TAG             IMAGE ID       CREATED          SIZE
spring/spring-getting-started                 latest          5f47030c5c3f   6 minutes ago    237MB
quarkus/quarkus-getting-started               distroless2     fe973c5ac172   24 minutes ago   49MB
quarkus/quarkus-getting-started               distroless      6fe27dd44e86   31 minutes ago   51MB
quarkus/quarkus-getting-started               ubi             8f86f5915715   58 minutes ago   132MB

The dilemma of Java application containerization

In the cloud native world, application containerization is a significant feature. When Java applications are containerized, they face the following problems:

  • Slow application startup: in fact, this is a problem with Java applications. Java applications occupy too much memory; When starting a JVM virtual machine, you need to initialize the environment, preload a large number of classes, initialize threads, and so on. The startup time is several seconds or even minutes depending on the application. Longer start-up time also inhibits horizontal scalability. Even in Serverless scenarios where the response time is not high, it will be rejected.
  • Too large image: in fact, the layered design of image is used, which is a common spring cloud application ̈ The BER jar package may have 7 or 80MB.
  • Space occupation: Although image layering is used, a little makes a lot, which will also increase the storage cost.

Quarkus and native image

Quarkus's development follows the principle of container first:

  • Graal/SubstrateVM support
  • Process metadata at build time
  • Reduce the use of reflections
  • Native image pre boot

Native images are technologies that precompile java code into executables called native images. The executable includes application classes, classes in their dependencies, runtime classes, and statically linked native code in the JDK. It does not run on the Java VM, but includes necessary components, such as memory management, thread scheduling, etc. these components come from another runtime system "substrate VM". "Substrate VM" is the name of the runtime component (e.g. de optimizer, garbage collector, thread scheduling, etc.). Compared with the JVM, the generated program has faster startup time and lower runtime memory overhead.

How to build a native image

Refer to the previous article for environment configuration. You can download the source code directly from here.

Configuring GraalVM

Previously, we used sdkman for GraalVM installation. set up   GRAALVM_HOME   Environment variables:

export GRAALVM_HOME=`sdk home java 21.0.0.2.r11-grl`

use   gu   install   native-image  :

${GRAALVM_HOME}/bin/gu install native-image

Build native executable

In the source code   pom.xml   In, we can see the following   profile  :

<profiles>
    <profile>
        <id>native</id>
        <properties>
            <quarkus.package.type>native</quarkus.package.type>
        </properties>
    </profile>
</profiles>

We use this profile to build the native executable, which takes a long time   A few minutes  .

./mvnw package -Pnative

Partial build log:

[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ quarkus-getting-started ---
[INFO]
[INFO] --- quarkus-maven-plugin:1.13.0.Final:build (default) @ quarkus-getting-started ---
[INFO] [org.jboss.threads] JBoss Threads version 3.2.0.Final
[INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building native image source jar: /Users/addo/Workspaces/private_w/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-native-image-source-jar/quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Building native image from /Users/addo/Workspaces/private_w/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-native-image-source-jar/quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildContainerRunner] Using docker to run the native image builder
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildContainerRunner] Checking image status quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11
21.0.0-java11: Pulling from quarkus/ubi-quarkus-native-image
Digest: sha256:becf08de869e707beaa5e57444b533ef93ebef15aad90c92ac660ddf7cea2b11
Status: Image is up to date for quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11
quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image plugin on GraalVM Version 21.0.0 (Java Version 11.0.10+8-jvmci-21.0-b06)
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildRunner] docker run --env LANG=C --rm -v /Users/addo/Workspaces/private_w/quarkus-getting-started/target/quarkus-getting-started-1.0.0-SNAPSHOT-native-image-source-jar:/project:z quay.io/quarkus/ubi-quarkus-native-image:21.0.0-java11 -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 -J-Duser.language=en -J-Duser.country=CN -J-Dfile.encoding=UTF-8 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy\$BySpaceAndTime -H:+JNI -H:+AllowFoldMethods -jar quarkus-getting-started-1.0.0-SNAPSHOT-runner.jar -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -J-Xmx5g -H:-AddAllCharsets -H:EnableURLProtocols=http --no-server -H:-UseServiceLoaderFeature -H:+StackTrace quarkus-getting-started-1.0.0-SNAPSHOT-runner
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]    classlist:   5,859.24 ms,  0.96 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]        (cap):     633.34 ms,  0.94 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]        setup:   2,468.19 ms,  0.94 GB
00:06:00,437 INFO  [org.jbo.threads] JBoss Threads version 3.2.0.Final
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]     (clinit):     516.65 ms,  2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]   (typeflow):  12,642.02 ms,  2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]    (objects):  11,340.37 ms,  2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]   (features):     525.87 ms,  2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]     analysis:  26,032.67 ms,  2.23 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]     universe:   1,394.06 ms,  2.16 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]      (parse):   2,690.38 ms,  2.16 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]     (inline):   4,336.77 ms,  2.73 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]    (compile):  17,580.03 ms,  2.71 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]      compile:  26,152.06 ms,  2.71 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]        image:   3,288.43 ms,  2.70 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]        write:   1,904.64 ms,  2.70 GB
[quarkus-getting-started-1.0.0-SNAPSHOT-runner:25]      [total]:  67,414.16 ms,  2.70 GB
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] objcopy executable not found in PATH. Debug symbols will not be separated from executable.
[WARNING] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] That will result in a larger native image with debug symbols embedded in it.
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 74739ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:21 min
[INFO] Finished at: 2021-04-17T08:06:47+08:00
[INFO] ------------------------------------------------------------------------

If something similar happens during construction   Caused by:
java.lang.RuntimeException: Image generation failed. Exit code was 137 which indicates an out of memory error. Consider increasing the Xmx value for native image generation by setting the "quarkus.native.native-image-xmx" property   This kind of error reporting. You need to adjust the Docker settings, such as the Mac OS used by the author, open Docker desktop > preference > resource > advanced, and increase the memory from the default 2GB, such as 8GB.

As can be seen from the build log, the build process is
quay.io/quarkus/ubi-quarkus-native-image   Completed in a container. Although the exception prompts adjustment“
Quarkus. Native. Native image Xmx "is actually caused by the small memory of the container.

After successful construction, you can   target   Found in
quarkus-getting-started-1.0.0-SNAPSHOT-runner  . This is an executable with a size of 28MB.

Attempt to execute the file, received   zsh: exec format error:
./target/quarkus-getting-started-1.0.0-SNAPSHOT-runner   Wrong. Because this is a Linux executable, we need to run it in the container.

Building native images

In the source file   src/main/docker   In the catalog, we can find   Dockerfile.native  :

FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
WORKDIR /work/
RUN chown 1001 /work \
    && chmod "g+rwX" /work \
    && chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application

EXPOSE 8080
USER 1001

CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

Run mirror

Run it locally. You can see that you only need to start it   0.013s  .

docker run --rm -p 8080:8080 quarkus/quarkus-getting-started:latest
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-04-17 00:22:27,146 INFO  [io.quarkus] (main) quarkus-getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 1.13.0.Final) started in 0.013s. Listening on: http://0.0.0.0:8080
2021-04-17 00:22:27,147 INFO  [io.quarkus] (main) Profile prod activated.
2021-04-17 00:22:27,147 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

Test the endpoint:

http :8080/hello/greeting/quarkus
HTTP/1.1 200 OK
Content-Length: 14
Content-Type: text/plain;charset=UTF-8

Hello, quarkus

Look at the image information. The size is 132MB, including base image   ubi-minimal   That's 103 MB. It still feels a little big. Do you want to continue to simplify it?

docker images
REPOSITORY                                    TAG             IMAGE ID       CREATED          SIZE
quarkus/quarkus-getting-started               latest          8f86f5915715   4 minutes ago    132MB
registry.access.redhat.com/ubi8/ubi-minimal   8.3             604ddd554fec   2 weeks ago      103MB

Mirror slimming

stay   src/main/docker   There is another one named
Dockerfile.native-distroless   Dockerfile, which uses
quay.io/quarkus/quarkus-distroless-image:1.0   Mirror as base

When you use this Dockerfile to build, the resulting image is much smaller, only 51MB:

docker images
REPOSITORY                                    TAG          IMAGE ID       CREATED          SIZE
quarkus/quarkus-getting-started               distroless   6fe27dd44e86   33 seconds ago   51MB
quarkus/quarkus-getting-started               ubi          8f86f5915715   27 minutes ago   132MB
quay.io/quarkus/quarkus-distroless-image      1.0          062663862a83   6 days ago       21.3MB
registry.access.redhat.com/ubi8/ubi-minimal   8.3          604ddd554fec   2 weeks ago      103MB

Run successfully:

docker run --rm -p 8080:8080 quarkus/quarkus-getting-started:distroless
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-04-17 00:51:26,070 INFO  [io.quarkus] (main) quarkus-getting-started 1.0.0-SNAPSHOT native (powered by Quarkus 1.13.0.Final) started in 0.013s. Listening on: http://0.0.0.0:8080
2021-04-17 00:51:26,071 INFO  [io.quarkus] (main) Profile prod activated.
2021-04-17 00:51:26,071 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy]

Extreme slimming, refer to here, we create
Dockerfile.native-distroless2 .

The size of the final image is 49MB, which is 2MB smaller than the official disaster base image.

docker images
REPOSITORY                                    TAG           IMAGE ID       CREATED          SIZE
quarkus/quarkus-getting-started               distroless2   fe973c5ac172   3 seconds ago    49MB

Compared with the above, it is used to build the base image of Spring applications   openjdk:11.0-jre-slim   There are already 220MB, which does not count the size of the application. even if it is   openjdk:17-alpine3.13   It also has 182 MB.

Original link:
https://atbug.com/quarkus-build-native-executable-file/

If you think this article is helpful, you can pay attention to my official account, scan the code below, pay attention to "w programming diary" and get more Java data.

Posted by rm_phpbuilder on Fri, 03 Dec 2021 07:32:49 -0800