Using Spring Native to Reduce Memory Footprint of Spring Boot Microservices
This article explains how to optimize memory usage and startup time of Spring Boot microservices on a limited-resource server by configuring JVM parameters, installing GraalVM, and building native images with Spring Native and Docker Buildpacks, providing step‑by‑step instructions and code examples.
Recently I built a microservice architecture with Spring Cloud Alibaba, but deploying seven services on a single 2C 4G Alibaba Cloud server caused memory shortages, as each service's fat‑jar Docker image consumed about 500 MB without JVM tuning.
Additional components such as Nacos, Redis, Sentinel, RocketMQ, and ELK further consumed over 2 GB, leaving insufficient memory for the services. To address this, I first added JVM parameters to limit memory usage:
# JVM initial heap size
-Xms128m
# JVM maximum heap size
-Xmx128m
# Thread stack size
-Xss256k
# Parallel GC threads (usually equal to CPU cores)
-XX:ParallelGCThreads=2Setting -Xms and -Xmx to the same value prevents heap resizing after each GC, reducing memory churn. However, even after tuning, each service still used 100‑200 MB, far from the desired tens of megabytes.
Research showed that Spring Native, which compiles Spring applications to native executables using GraalVM, can dramatically lower memory consumption and startup time (millisecond‑level startup, ~50 MB image size).
Spring Native benefits:
Extremely fast startup (milliseconds).
Lower runtime memory usage (officially ~50 MB for a Spring Boot + MVC + Tomcat image).
Longer build times (a simple Hello World may take ~2 minutes).
Spring Native requires Java 11 or 17 and can be built via two main methods:
Using Spring Boot Buildpacks to generate a lightweight container with a native executable.
Using the GraalVM native-image Maven plugin to produce a native binary directly.
For my Docker‑based deployment, I chose method 1, which integrates with the spring-boot-maven-plugin and Buildpacks.
Installation Steps
1. Install GraalVM (graalvm‑ce‑java11‑windows‑amd64) – download from https://www.graalvm.org/downloads/ and set JAVA_HOME (optionally also add GRAALVM_HOME and update PATH ).
2. Install native‑image component – run gu install native-image (or manually download the JAR from GitHub if the command fails).
gu install native-image3. Configure pom.xml – add Spring Native dependency, the spring-aot-maven-plugin , and enable native image building in the spring-boot-maven-plugin configuration:
<project ...>
<properties>
<java.version>11</java.version>
<spring-native.version>0.11.1</spring-native.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.1</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>Additional JVM options can be added under the configuration section, for example:
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
<BPE_APPEND_JAVA_TOOL_OPTIONS>-Xms128m</BPE_APPEND_JAVA_TOOL_OPTIONS>
<BPE_APPEND_JAVA_TOOL_OPTIONS>-Xmx128m</BPE_APPEND_JAVA_TOOL_OPTIONS>
<BPE_APPEND_JAVA_TOOL_OPTIONS>-Xss256k</BPE_APPEND_JAVA_TOOL_OPTIONS>
<BPE_APPEND_JAVA_TOOL_OPTIONS>-XX:ParallelGCThreads=2</BPE_APPEND_JAVA_TOOL_OPTIONS>
</env>
</image>4. Build the native image – run the Maven commands:
mvn clean
mvn '-Dmaven.test.skip=true' spring-boot:build-imageThe build process is CPU‑intensive, often reaching 90%+ CPU usage, but completes successfully, producing a Docker image named spring-native .
5. Run the container – use Docker Desktop to start the image. The application starts in about 59 ms and consumes roughly 28 MB of memory, compared to the non‑native version which takes ~3 seconds to start and uses over 500 MB.
These results demonstrate the significant performance and memory benefits of Spring Native for Java microservices, though the technology is still evolving and should be used cautiously in production.
For more details, refer to the official Spring Native documentation at https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/index.html and the Spring Boot Buildpacks guide at https://docs.spring.io/spring-boot/docs/2.6.2/maven-plugin/reference/htmlsingle/#build-image .
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.