• Latest
Master Spring Boot 3 With GraalVM Native Image

Master Spring Boot 3 With GraalVM Native Image

March 22, 2023
Minecraft’s new Dungeons & Dragons DLC rolls out soon

Minecraft’s new Dungeons & Dragons DLC rolls out soon

March 29, 2023
Atlas Fallen Delayed To August

Atlas Fallen Delayed To August

March 29, 2023
OPD GAMING IS LIVE 🥰🥰🥰 PLAYING WITH SUBSCRIBERS

OPD GAMING IS LIVE 🥰🥰🥰 PLAYING WITH SUBSCRIBERS

March 29, 2023
Borrowing e-books from libraries will require Libby; OverDrive retired

Borrowing e-books from libraries will require Libby; OverDrive retired

March 29, 2023
Samsung Galaxy Z Fold5 and Z Flip5 run Geekbench with SD 8 Gen 2 for Galaxy chipset

Samsung Galaxy Z Fold5 and Z Flip5 run Geekbench with SD 8 Gen 2 for Galaxy chipset

March 29, 2023
A New Retro Gaming Podcast Just Launched… By Us

A New Retro Gaming Podcast Just Launched… By Us

March 29, 2023
Indian Bike Driving 3D Dog Cheat code #games #indianbikediving #gtav #viral #trending #dog

Indian Bike Driving 3D Dog Cheat code #games #indianbikediving #gtav #viral #trending #dog

March 29, 2023
Score Up To 30% Off Selected Shmups In Live Wire’s Spring eShop Sale

Score Up To 30% Off Selected Shmups In Live Wire’s Spring eShop Sale

March 29, 2023
Resident Evil 4 remake hits 25% of original’s lifetime sales in 48 hours

Resident Evil 4 remake hits 25% of original’s lifetime sales in 48 hours

March 29, 2023
Indian Bike Driving 3D gameplay Use cheat codes dog, bikes etc must watch #Indianbikedriving #gtav

Indian Bike Driving 3D gameplay Use cheat codes dog, bikes etc must watch #Indianbikedriving #gtav

March 29, 2023
AI development beyond GPT-4 should be paused – Woz, Musk

AI development beyond GPT-4 should be paused – Woz, Musk

March 29, 2023
Gripper Review (Switch eShop) | Nintendo Life

Gripper Review (Switch eShop) | Nintendo Life

March 29, 2023
Advertise with us
Wednesday, March 29, 2023
Bookmarks
  • Login
  • Register
GetUpdated
  • Game Updates
  • Mobile Gaming
  • Playstation News
  • Xbox News
  • Switch News
  • MMORPG
  • Game News
  • IGN
  • Retro Gaming
  • Tech News
  • Apple Updates
  • Jailbreak News
  • Mobile News
  • Software Development
  • Photography
  • Contact
No Result
View All Result
GetUpdated
No Result
View All Result
GetUpdated
No Result
View All Result
ADVERTISEMENT

Master Spring Boot 3 With GraalVM Native Image

March 22, 2023
in Software Development
Reading Time:15 mins read
0 0
0
Share on FacebookShare on WhatsAppShare on Twitter


Spring Boot 3 is riding the wave in the Java world: a few months have passed since the release, and the community has already started migrating to the new version. The usage of parent pom 3.0.2 is approaching 500 on Maven Central!

An exciting new feature of Spring Boot is the baked-in support from GraalVM Native Image. We have been waiting for this moment for years. The time to migrate our projects to Native Image is now!

But one cannot simply transfer the existing workloads to Native Image because the technology is incompatible with some Java features. So, this article covers the intricacies associated with Spring Boot Native Image development.

A Paradigm Shift in Java Development

For many years, dynamism was one of the essential Java features. Developer tools written in Java, such as IntelliJ IDEA and Eclipse, are built upon the presumption that “everything is a plugin,” so we can load as many new plugins as we like without restarting the development environment. Spring is also an excellent example of a dynamic environment, thanks to such features as AOP. 

Years have passed. We discovered that dynamic code loading is not only convenient but also resource-expensive. Waiting 20 minutes for a web application to start is not fun. We had to think of ways to accelerate startup and reduce excessive memory consumption. As a result, developers began abstaining from excessive dynamism and statically precompiling all necessary resources.

Then, GraalVM Native Image appeared. The technology turns a JVM-based application into a compiled binary, which sometimes doesn’t require a JDK to run. The resulting native binary starts up incredibly fast.

But Native Image works under the “closed-world assumption,” i.e., all utilized classes must be known during the compilation. So, the migration to Native Image is not about changing certain lines of code. It is about shifting the development approach. Your task is to make dynamic resources known to the Native Image at the compilation stage.

Native Image Specifics

Finalization

Developing a Spring application is not the same as writing a bare Java app. We should keep that in mind when working with Native Image. To bring Spring and Native Image together, you must dig into some Java peculiarities.

For example, let’s take a simple case of class finalization. At the beginning of Java evolution, we could write some housekeeping code in finalize(), set the System.runFinalizersOnExit(true) flag, and wait for the program to exit.

public class ShutdownHookedApp

{

    public static void main( String[] args )

    {

        System.runFinalizersOnExit(true);

    }



    protected void finalize() throws Throwable {

        System.out.println( "Goodbye World!" );

    }

}

You will be surprised if you expect a “Goodbye World!” output because this code won’t run with the existing Java versions due to garbage collection specifics. With Java versions 8-10, the app will do nothing, but with Java 11, it will throw an exception with a message that this feature was deprecated:

➜  shutdown_hook_jar java -jar ./shutdown-hook.jar

Exception in thread “main” java.lang.NoSuchMethodError: void java.lang.System.runFinalizersOnExit(boolean)

    at ShutdownHookedApp.main(ShutdownHookedApp.java:9)

Why was this feature removed? Finalizers work in some situations and fail in others. Developers can’t rely on a feature with such unpredictable behavior.

Native Image documentation states that finalizers don’t work and must be substituted with weak references, reference queues, or something else, depending on the situation.

For the Java platform, it is a good development trend to make unpredictable behavior a thing of the past. If you want to guarantee the String output upon exit, use Runtime.getRuntime().addShutdownHook():

public class ShutdownHookedApp

{

    public static void main( String[] args )

    {

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {

            System.out.println("Goodbye World!");

        }));

    }

}

Spring developers have additional tools. You can use the @PreDestroy annotation and close the context manually with ConfigurableApplicationContext.close() or write something similar to shutdown hook registration.

You can do this instead of a finalizer:

@Component

public class WorldComponent {

    @PreDestroy

    public void bye() {

        System.out.println("Goodbye World!");

    }

}

Or you can use this instead of Shutdown Hook:

@SpringBootApplication

public class PredestroyApplication {



    public static void main(String[] args) {

        ConfigurableApplicationContext ctx = SpringApplication.run(PredestroyApplication.class, args);



        int exitCode = SpringApplication.exit(ctx, new ExitCodeGenerator() {

            @Override

            public int getExitCode() {

                        System.out.println("Goodbye World!");

                return 0;

            }

        });



        System.exit(exitCode);

    }

}

Now, let’s collect all these methods in one code snippet:

@SpringBootApplication

public class PredestroyApplication {



    public static void main(String[] args) {



        Runtime.getRuntime().addShutdownHook(new Thread(() -> {

          System.out.println("Goodbye World! (shutdown-hook)");

        }));



        ConfigurableApplicationContext ctx = SpringApplication.run(PredestroyApplication.class, args);



        int exitCode = SpringApplication.exit(ctx, new ExitCodeGenerator() {

            @Override

            public int getExitCode() {

                System.out.println("Goodbye World! (context-exit)");

                return 0;

            }

        });



        System.exit(exitCode);

    }



    @PreDestroy

    public void bye() {

        System.out.println("Goodbye World! (pre-destroy)");

    }



    @Override

    protected void finalize() throws Throwable {

        System.out.println( "Goodbye World! (finalizer)" );

    }

}

Let’s run the program and see the order of our “finalizers”:

Goodbye World! (context-exit)

Goodbye World! (pre-destroy)

Goodbye World! (shutdown-hook)

This code will function when compiling a Spring app into a native image.

Initialization

Let’s set finalization aside and look into initialization for a change. Spring provides several field initialization methods: you can assign a value directly with the @Value annotation or define properties with @Autowired or @PostConstruct. GraalVM adds another interesting technique, which enables you to write data at the binary compilation stage. Classes that you want to initialize this way are marked with --initialize-at-build-time=my.class when building Native Image. The option works for the whole class, not just separate fields. It is convenient and sometimes even required (if you use Netty, for example).

Let’s build a new Spring application with Spring Initializr. The only dependency we need to specify is GraalVM Native Support.

You also need a Native Image Build Tool to generate native executables. BellSoft develops Liberica Native Image Kit, a GraalVM-based utility recommended by Spring. Download Liberica NIK for your platform here. Select NIK 22 (JDK 17), Full version.

Put the compiler to $PATH with

GRAALVM_HOME=/home/user/opt/bellsoft-liberica

export PATH=$GRAALVM_HOME/bin:$PATH

Check that Liberica NIK is installed:

java -version

openjdk version "17.0.5" 2022-10-18 LTS

OpenJDK Runtime Environment GraalVM 22.3.0 (build 17.0.5+8-LTS)

OpenJDK 64-Bit Server VM GraalVM 22.3.0 (build 17.0.5+8-LTS, mixed mode, sharing)

native-image --version

GraalVM 22.3.0 Java 17 CE (Java Version 17.0.5+8-LTS)

Back to Spring Boot. Our application will do the following logic: we initialize a PropsComponent bean and ask it for a key:

@SpringBootApplication

public class BurningApplication {



    @Autowired

    PropsComponent props;



    public static void main(String[] args) {

        SpringApplication.run(BurningApplication.class, args);

    }



    @PostConstruct

    public void displayProperty() {

        System.out.println(props.getProps().get("key"));

    }

}

Component properties will be loaded in a static class initializer.

@Component

public class PropsComponent {

    private static final String NAME = "my.properties";

    private static final Properties props;

    public static final String CONFIG_FILE = "/tmp/my.props";



    static {

        Properties fallback = new Properties();

        fallback.put("key", "default");

        props = new Properties(fallback);

        try (InputStream is = new FileInputStream(CONFIG_FILE)) {

            props.load(is);

        } catch (IOException ex) {

            throw new UncheckedIOException("Failed to load resource", ex);

        }

    }



    public Properties getProps() {

        return props;

    }

}

Create a /tmp/my.props text file and populate it with data:

key=apple

If we build the standard Java app, we get different outputs by changing the contents of the my.props file. But we can change the rules of the game. Let’s write the following Native Image configuration in our pom.xml:

   <profiles>

        <profile>

            <id>native</id>

            <build>

                <plugins>

                    <plugin>

                        <groupId>org.graalvm.buildtools</groupId>

                        <artifactId>native-maven-plugin</artifactId>

                        <executions>

                            <execution>

                                <id>build-native</id>

                                <goals>

                                    <goal>compile-no-fork</goal>

                                </goals>

                                <phase>package</phase>

                            </execution>

                        </executions>

                        <configuration>

                            <buildArgs>

                                --initialize-at-build-time=org.graalvm.community.examples.burning.PropsComponent

                            </buildArgs>

                        </configuration>

                    </plugin>

                </plugins>

            </build>

        </profile>

    </profiles>

Pay attention to the initialize-at-build-time key. Now, build the app with mvn clean package —Pnative. The resulting file is in the target directory. No matter how many times we change the /tmp/my.properties file, the output will be the same (the one we wrote at compilation).

On the one hand, it is an excellent tool that increases application portability if you use the properties file for code organization and not for dynamic String loading. On the other hand, it may lead to misuse and incorrect understanding of the code. For example, a DevOps may glance at the code, see the my.properties file, and then spend the whole day trying to understand why the file doesn’t pick his or her settings.

This is a fundamental concept of Native Image — the separation of data between compilation and run time. If you build app configuration based on environment variables, you should evaluate which keys will be initialized at a specific moment. For convenience’s sake, it is possible to use different prefixes, like S_ for static compilation and D_ for dynamic one.

Native Image Limitations

Some functions work differently or don’t work with GraalVM at all:

  • Reflection
  • Proxies
  • Method Handles
  • Serialization
  • JNI
  • Resources

One approach is to accept that they are not supported and rewrite the app accordingly. Another way is to understand what we know at compile time and put this data into config files.

Below is the example for Reflection:

[

  {

    "name": "HelloWorld",

    "allDeclaredFields": true

  }

]

To avoid manual configuration, run the app with the standard JVM and the java -agentlib:native-image-agent=config-output-dir=./config flag. While you are using the application, all resources you are utilizing will be written into the config directory.

The agent usually generates a whole bunch of files associated with the features mentioned above:

  • jni-config.json
  • predefined-classed-config.json
  • proxy-config.json
  • reflect-config.json
  • resource-config.json
  • serialization-config.json

After that, state these files in pom.xml Spring configuration:

<groupId>org.graalvm.buildtools</groupId>

<artifactId>native-maven-plugin</artifactId>

<configuration>

    <buildArgs>

        -H:ReflectionConfigurationFiles=reflect-config.json

    </buildArgs>

</configuration>

If you adjust the Reflection as shown above and then try to access something you didn’t define, the program won’t exit with an error like “Aborting stand-alone image build due to reflection use without configuration.” Instead, it will continue running, and Reflection calls will provide an empty output. It means that you must consider all Reflection calls when writing the tests.

Compatibility With Legacy Libraries

The Java ecosystem has a competitive edge over C/C++. With Java, you can add a couple of lines to the pom.xml, and Maven will load a ready-to-use library. In contrast, a C++ developer must put libraries together manually for different platforms. Classical Java enables you to use third-party libraries as ready-to-go boxes without knowing how they were developed. 

The situation differs with Native Image. Because Native Image uses global code analysis, it compiles the libraries with the application code. Third-party libraries are compiled every time anew on your computer. If there is a compilation error, you will have to solve the issues related to the incompatible library.

If you develop an innovative solution, these GraalVM incompatibilities are a great way to find issues in your code or discover new ways of developing your projects. If you write hardcore fintech code, determine whether the third-party library supports GraalVM Native Image.

But let’s go back to Spring: what about Native image support there? The Spring team has done outstanding work with integrating Native Image technology into the ecosystem. Just a year ago, Spring didn’t support the technology. Then the Spring Native project was born, and now, Spring Boot has baked-in support for Native Image.

The team continues building on the momentum, and many Spring libraries and modules are already compatible with Native Image. Still, we recommend running the libraries with your code using a prototype to make sure that everything works correctly.

Development and Debugging Intricacies

Native Image compilation takes time (min. 90 seconds), so it would be more practical to write and debug the code using standard JVM and turn to Native Image only when you have some coherent results. But you should always test the resulting binary separately, even if you double-checked the JAR. Why? An application compiled with Native Image can behave differently than the “classic” JVM version. For example, you have an inexplicit Reflection somewhere in the code. It fails without an error or message, and the code gives a different result.

To accelerate the testing process, set the CI server in such a way that it collects all commits through Native Image. You can also save testers’ time by providing them with Native Image binaries only, without the JARs.

In addition, DevOps engineers should write a console script that can be easily started (ondemand ./project). It builds the project, compiles it with Native Image, packs it into a Docker image, and deploys it to a new virtual machine on Amazon. Fortunately, Spring Boot performs the whole build process with a single command: mvn clean package -Pnative. But virtual machine deployment remains your task.

Performance Profile

Legends and mysteries surround Native Image advantages. They claim it will make apps smaller and faster. But what does it mean, “smaller” and “faster”?

Two decades ago, before cloud computing and microservices, developers wrote monolithic applications only (they still prevail in some industries, such as gaming). The key performance indicators for monolithic applications are increased raw peak performance and minimal latency. The Just-in-time (JIT) compiler built into the OpenJDK is quite good at dealing with these tasks. But the performance increases only after Tier4CompileThreshold invokes the C2 JIT-compiler, which takes time. It is not optimal for cloud-native microservices.

Key performance indicators are different in the cloud:

  • Microservices have to restart rapidly for efficient scaling;
  • Containers must consume less resources so as not to inflate cloud bills;
  • The build process must be simple simple to make the DevOps processes easier;
  • Packages must be small so that developers can rapidly solve issues and move apps between the Kubernetes nodes. 

The JIT compiler is not suitable for these purposes because of long warm-up time and excessive overhead.

GraalVM uses the AOT (ahead-of-time) compilation, which significantly reduces startup time. As for the memory consumption, the resulting native executable is not always smaller than Uber JAR or Fat JAR. So developers should build the project or its part with Native Image and verify whether it is worth the trouble.

There is one more thing to consider when selecting the compiler, namely the load patterns. The AOT compilation is best suitable for a “flat” load profile when application parts are loaded understandably. JIT is optimal for applications with sudden load peaks because JIT can define and optimize such loads. The choice depends on your app.

Take a look at your Spring Boot app. Find out which microservices return web pages, which work with a database, and which perform complex analytics. We can safely assume that the load profile of web services will be flatter than that of business analytics, so they potentially can be migrated to Native Image.

Garbage Collection in Native Image

Native Image is a relatively new project, so it doesn’t utilize the variety of garbage collectors compared to OpenJDK. GraalVM Community Edition (and Liberica NIK) currently use only simple Serial GC with generations (generational scavenger). Oracle’s GraalVM Enterprise also has a G1 GC. 

The first thing to note is that Native Image uses more memory than stated in the Xmx parameter. A standard JVM-based app does that too, but for different reasons. In the case of Native Image, the root cause resides in garbage collection specifics. GC uses additional memory when performing its tasks.

If you run your application in a container and set the precise amount of memory in Xmx, it will probably go down when loads increase. Therefore, you should allocate more memory. Use a trial-and-error approach to find out how much.

Furthermore, if you write a tiny program, it doesn’t mean it will automatically use less memory. Like with JVM, we have Xmx (maximum heap size in bytes) and Xmn (young generation size) parameters. If you don’t state them, the app may devour all available memory within the limit. You can alleviate the situation with the -R:MaxHeapSize parameter that sets the default heap size at build time.

But thanks to these Native Image specifics, we can now conveniently write console applications with Spring Boot. Imagine you want to create a console client for your web service. The first thought that comes to mind is to develop a Spring Boot app and reuse the whole Java code, including classes for API. But such an application would take several seconds to start without a chance for acceleration with JIT because there’s too little runnable code in console apps to trigger JIT. And every application start would consume a lot of RAM.

Now you can compile the app with Native Image, set -R:MaxHeapSize, and get a good result, no worse than with standard Linux console commands.

For illustrative purposes, I wrote a console jls utility with the same function as ls, i.e., listing the files. The algorithm is borrowed from StackOverflow.

@SpringBootApplication

public class JLSApplication {



    public static void main(String[] args) {

        SpringApplication.run(JLSApplication.class, args);

        walkin(new File(args[0]));

    }



    public static void walkin(File dir) {



        File listFile[] = dir.listFiles();

        if (listFile != null) {

            for (int i=0; i<listFile.length; i++) {

                if (listFile[i].isDirectory()) {

                    System.out.println("|tt");

                    walkin(listFile[i]);

                } else {



                    System.out.println("+---"+listFile[i].getName().toString());



                }

            }

        }

    }

}

Define the max. heap size in Maven settings:

<configuration>

    <buildArgs>

        -R:MaxHeapSize=2m

    </buildArgs>

</configuration>

According to the time utility, the execution time for the /tmp directory is about 0.02 s, which is within the time margin of error. And that time includes the start of the algorithm plus the whole Spring Boot app. The result is quite impressive compared to a JAR file.

Conclusion

Finally, we can compile our Spring Boot projects with Native Image! This powerful utility makes it possible to perform tasks previously unattainable for Java developers.



Source link

ShareSendTweet
Previous Post

Rivian R1T After 500 Miles!

Next Post

Microsoft Wins In Gamer-Led Antitrust Lawsuit To Stop Activision Blizzard Acquisition, For Now

Related Posts

Technical Skills of a Software Dev. Partner

March 29, 2023
0
0
Technical Skills of a Software Dev. Partner
Software Development

Software development plays a crucial role, leading many companies to outsource this function to third-party service providers. However, selecting the...

Read more

Redefining the Boundaries of People, Process, and Platforms

March 29, 2023
0
0
Redefining the Boundaries of People, Process, and Platforms
Software Development

Day two of Dynatrace Perform began with a great discussion between Kelsey Hightower, Distinguished Developer Advocate at Google Cloud Platform...

Read more
Next Post
Microsoft Wins In Gamer-Led Antitrust Lawsuit To Stop Activision Blizzard Acquisition, For Now

Microsoft Wins In Gamer-Led Antitrust Lawsuit To Stop Activision Blizzard Acquisition, For Now

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

© 2021 GetUpdated – MW.

  • About
  • Advertise
  • Privacy & Policy
  • Terms & Conditions
  • Contact

No Result
View All Result
  • Game Updates
  • Mobile Gaming
  • Playstation News
  • Xbox News
  • Switch News
  • MMORPG
  • Game News
  • IGN
  • Retro Gaming
  • Tech News
  • Apple Updates
  • Jailbreak News
  • Mobile News
  • Software Development
  • Photography
  • Contact

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms bellow to register

All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
Are you sure want to unlock this post?
Unlock left : 0
Are you sure want to cancel subscription?