Enterprise Java

Memory Usage Optimization In Spring Boot

1. Introduction

A basic Spring Boot application with an embedded Tomcat server would consume 100 MB of memory when launched. JVM memory consists of HeapMetaspaceOverhead, and Native memories.

  • Heap is used for object storage and garbage collection. The garbage collector automatically manages memory by reclaiming space from unused objects. The heap is the largest chunk of memory and might allocate between 50% and 80% of the total available system memory for the JVM.
  • Metaspace is used to store metadata about classes. It replaces PermGen since Java 8+
  • Overhead is used for thread management, etc.
  • Native is used to interact with the operating system.

In this example, I will explain why this happens and examine ways to reduce memory usage without impacting application functionality.

2. Setup

2.1 Spring Boot 3.x Web Application

In this step, I will create a Gradle Java 21 Spring Boot 3 project via Spring IO Initializr with the following common libraries:

  • Spring Web
  • Lombok Developer Tools
  • Validation I/O
  • Spring Security
  • Spring Boot Actuator Ops
  • Spring Session for Spring Data Redis Web
  • Config Client Spring Cloud Config

Unzip the generated zip file and output the generated build.gradle.

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.10'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'org.jcg.zheng.demo21G'
version = '0.0.1-SNAPSHOT'
description = 'Demo memory usage project for Spring Boot'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(21)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "2024.0.2")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.cloud:spring-cloud-config-server'
	implementation 'org.springframework.session:spring-session-data-redis'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

2.2 Install VisualVM

In this step, I will install the VisualVM tool as it isn’t included in JDK21.

2.3 Start the Spring Boot Application

In this step, I will start the Spring Boot application created at step 2.1 without any modification via the “gradlew bootRun” command and capture the server log.

gradlew bootRun

C:\MaryZheng\workspace\sbmemory21g>gradlew bootRun
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::               (v3.4.10)

2025-09-21T17:42:07.553-05:00  INFO 19768 --- [sbmemory21g] [           main] o.j.z.d.s.Sbmemory21gApplication         : Starting Sbmemory21gApplication using Java 21.0.7 with PID 19768 (C:\MaryZheng\workspace\sbmemory21g\build\classes\java\main started by zzhen in C:\MaryZheng\workspace\sbmemory21g)
2025-09-21T17:42:07.555-05:00  INFO 19768 --- [sbmemory21g] [           main] o.j.z.d.s.Sbmemory21gApplication         : No active profile set, falling back to 1 default profile: "default"
2025-09-21T17:42:08.185-05:00  INFO 19768 --- [sbmemory21g] [           main] o.s.cloud.context.scope.GenericScope     : BeanFactory id=2723bc09-5e66-38ec-96ea-3c4721fe1b70
2025-09-21T17:42:08.382-05:00  INFO 19768 --- [sbmemory21g] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2025-09-21T17:42:08.394-05:00  INFO 19768 --- [sbmemory21g] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2025-09-21T17:42:08.395-05:00  INFO 19768 --- [sbmemory21g] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.46]
2025-09-21T17:42:08.431-05:00  INFO 19768 --- [sbmemory21g] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2025-09-21T17:42:08.432-05:00  INFO 19768 --- [sbmemory21g] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 847 ms
2025-09-21T17:42:08.794-05:00  WARN 19768 --- [sbmemory21g] [           main] .s.s.UserDetailsServiceAutoConfiguration :

Using generated security password: b102c837-4262-4e5f-b418-f3a6798998f6

This generated password is for development use only. Your security configuration must be updated before running your application in production.

2025-09-21T17:42:08.805-05:00  INFO 19768 --- [sbmemory21g] [           main] r$InitializeUserDetailsManagerConfigurer : Global AuthenticationManager configured with UserDetailsService bean with name inMemoryUserDetailsManager
2025-09-21T17:42:08.875-05:00  INFO 19768 --- [sbmemory21g] [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
2025-09-21T17:42:08.953-05:00  INFO 19768 --- [sbmemory21g] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2025-09-21T17:42:08.963-05:00  INFO 19768 --- [sbmemory21g] [           main] o.j.z.d.s.Sbmemory21gApplication         : Started Sbmemory21gApplication in 1.703 seconds (process running for 2.002)
 80% EXECUTING [1h 58m 25s]

The server log shows the Spring Boot 3.4.10 web application is started.

3. Monitor Via VisualVM

Launch VisualVM and monitor the basic Spring Boot application started at step 2.3.

How to Reduce Spring Boot Memory Usage
Figure 1. JVM Monitor
  • The pid is 19768.
  • The current committed heap size is 104857600B. It is between -Xms and -Xmx.
  • The actual used heap size is ranged from 25MB to 90MB.
  • The max heap size is 4G and can be set by the -Xmx
  • The Metaspace size average is 42MB.
  • The total loaded classes count is 10505.

So, this Spring boot application consumes about 67MB-132MB memory. Here are the main reasons why it takes that much memory:

  • Spring Boot Framework overhead – spring context initialized thousands of classes, dependency injection graph resolved, and stored reflection metadata.
  • Embedded Servlet Container – Tomcat threads, request/response buffers, servlet mappings, and with default 200 threads pool.
  • Logging – Logging initialization and configuration.
  • Annotation scanning for @Configuration, @Component, @Entity, and @Service can add lots of objects.
  • Spring Boot Actuator includes health checks and metrics.
  • Jackson/JSON libraries cache type metadata.

4. Monitor via Commands

4.1 The Jcmd command

You can get the memory usage via the jcmd command.

jcmd 19768 GC.heap_info

C:\Program Files\Java\jdk-21\bin>jcmd 19768 GC.heap_info
19768:
 garbage-first heap   total 102400K, used 62525K [0x0000000704e00000, 0x0000000800000000)
  region size 2048K, 22 young (45056K), 4 survivors (8192K)
 Metaspace       used 40041K, committed 40640K, reserved 1114112K
  class space    used 5635K, committed 5888K, reserved 1048576K
  • Line 3: Heap size: 102400k, used heap size is 62525K
  • Line 5: Metaspace used size: 40041K.

Here is another example with the jcmd command and listing the first 10 items.

jcmd 13352 GC.class_histogram

C:\MaryZheng\workspace\sbmemory21g>jcmd 13352 GC.class_histogram
13352:
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:         68467        8375992  [B ([email protected])
   2:         66137        1587288  java.lang.String ([email protected])
   3:         10956        1294000  java.lang.Class ([email protected])
   4:         33264        1064448  java.util.concurrent.ConcurrentHashMap$Node ([email protected])
   5:         12388         667024  [Ljava.lang.Object; ([email protected])
   6:          5335         656432  [I ([email protected])
   7:         13316         532640  java.util.LinkedHashMap$Entry ([email protected])
   8:          7755         496320  java.util.LinkedHashMap ([email protected])
   9:          6564         473048  [Ljava.util.HashMap$Node; ([email protected])
  10:           334         327328  [Ljava.util.concurrent.ConcurrentHashMap$Node; ([email protected])

4.2 The Jmap Command

The jmap command outputs the retained size and order by the descending order.

jmap -histo

C:\MaryZheng\workspace\sbmemory21g>jmap -histo 13352
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:        119432       12819960  [B ([email protected])
   2:        111350        4506600  [Ljava.lang.Object; ([email protected])
   3:         62023        2480920  java.util.TreeMap$Entry ([email protected])
   4:        102408        2457792  java.lang.String ([email protected])
   5:         10491        1645864  [I ([email protected])
   6:         11163        1318736  java.lang.Class ([email protected])
   7:         36762        1176384  java.util.concurrent.ConcurrentHashMap$Node ([email protected])
   8:          3451        1109904  [C ([email protected])
   9:         11883        1045704  java.lang.reflect.Method ([email protected])
  10:         16221        1038144  java.util.LinkedHashMap ([email protected])

4.3 The Jstat Command

The jstat command outputs the memory usage for the gc process every 10 seconds.

jstat -gc 1000

C:\MaryZheng\workspace\sbmemory21g>jstat -gc 13352 1000
    S0C         S1C         S0U         S1U          EC           EU           OC           OU          MC         MU       CCSC      CCSU     YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT
        0.0      2048.0         0.0        23.0      47104.0          0.0      26624.0      20984.7    42112.0    40993.7    6144.0    5744.2     11     0.042     2     0.047     4     0.004     0.094
        0.0      2048.0         0.0        23.0      47104.0          0.0      26624.0      20984.7    42112.0    40993.7    6144.0    5744.2     11     0.042     2     0.047     4     0.004     0.094
        0.0      2048.0         0.0        23.0      47104.0       2048.0      26624.0      20984.7    42112.0    40993.7    6144.0    5744.2     11     0.042     2     0.047     4     0.004     0.094
  • S0C/S0U, S1C/S1U: Survivor spaces capacity/used
  • EC/EC: Eden capacity/used
  • OC/OU: Old generation capacity/used
  • MC/MU: Metaspace capacity/used
  • CCSC/CCSU: Compressed class space capacity/used

4.4 Find the Total Physical Memory

In this step, I will use the systeminfo command to find my PC’s total physical memory.

systeminfo | find “Total Physical Memory”

C:\MaryZheng\workspace\sbmemory21g>systeminfo | find "Total Physical Memory"
Total Physical Memory:         16,069 MB

C:\MaryZheng\workspace\sbmemory21g>

My PC’s total physical memory is 16GB, so the MaxHeapSize cannot exceed 4GB(1/4 of total physical memory).

5. Memory Usage Optimization

In this step, I will adjust the Spring Boot web application’s JVM memory options, Tomcat thread max count, Spring Bean lazy initialization and compare its memory footprint to the data captured at step 3 and 4.

5.1 Adjust JVM Memory Options

Please refer to my other article for adjusting the JVM memory options. Here are common JVM memory options:

  • -Xms# sets the initial heap size when the JVM starts.
  • -Xmx# sets the maximum heap size for the JVM. Setting both initial and maximum heap size to the same value can avoid the overhead for heap resizing, but it can waste memory if not used.
  • -Xmn# sets the size of the young generation (part of the heap).
  • -Xss# sets the stack size for each thread.
  • -XX:+HeapDumpOnOutOfMemoryError generates a heap dump when an OutOfMemoryError occurs.
  • -XX:HeapDumpPath={path_to_dump_file} specifies the path location.
  • -XX:+UseG1GC: Use the G1 garbage collector.
  • -XX:+UseConcMarkSweepGC: Use the Concurrent Mark-Sweep (CMS) garbage collector.
  • -XX:+UseParallelGC: Use the parallel garbage collector.
  • -XX:+AggressiveOpts: Enable aggressive optimization settings for performance.
  • -XX:MaxInlineLevel: Control the maximum number of methods that can be inlined.
  • -XX:MaxMetaspaceSize: Set the maximum size of the Metaspace after JDK8.
  • -XX:ReservedCodeCachedSize: Reserve code cached size in bytes.
  • -XX:+UseContainerSupport: This is enabled by default since JDK10
  • -XX:MaxRAMPercentage: Configure the memory with percentage instead of hard code value.

5.2 Adjust Tomcat Thread Configuration

In this step, I will adjust the Spring Boot Application Tomcat server configuration by setting the max Tomcat thread count to 4 (the Spring Boot application has a default max thread count of 200).

application.properties

spring.application.name=sbmemory21g

//default 200
server.tomcat.threads.max=4
//default 10
server.tomcat.threads.min-space=2

After updating the Tomcat threads setting, start the Spring Boot application and capture its memory footprint via both VisualVM and the command line outlined at step 3 and 4.

jcmd GC.heap_info output

C:\MaryZheng\workspace\sbmemory21g>jcmd 14200 GC.heap_info
14200:
 garbage-first heap   total 83968K, used 32453K [0x0000000704e00000, 0x0000000800000000)
  region size 2048K, 3 young (6144K), 1 survivors (2048K)
 Metaspace       used 41358K, committed 42048K, reserved 1114112K
  class space    used 5840K, committed 6144K, reserved 1048576K

Compare its Heap, Metaspace, threads, classes loaded to the data captured at step 3.

Basic Spring Boot with Default 200 threadsUpdated Spring Boot with 4 ThreadsComparison Results
Heap Used62525K32453Kreduced by 48%
Metaspace40041K41358Kincreased by 3%
Total Thread Started3428reduced
Total Classes Loaded1050510488reduced

It trades memory footprint with throughput by reducing the thread count.

5.3 Adjust Lazy Bean Initialization

In this step, I will adjust the Spring Boot Application lazy bean configuration.

application.properties

spring.main.lazy-initialization=true

After updating the Tomcat thread setting, start the Spring Boot application and capture the memory usage.

application.properties.java

C:\MaryZheng\workspace\sbmemory21g>jcmd 25168 GC.heap_info
25168:
 garbage-first heap   total 98304K, used 53585K [0x0000000704e00000, 0x0000000800000000)
  region size 2048K, 16 young (32768K), 3 survivors (6144K)
 Metaspace       used 38358K, committed 39040K, reserved 1114112K
  class space    used 5376K, committed 5696K, reserved 1048576K

Compare its Heap, Metaspace, thread, classes loaded to the data captured at step 3.

Basic Spring Boot with Default SettingUpdated Spring Boot Lazy Bean InitializationComparison Results
Heap Used62525K53585Kreduced by 15%
Metaspace40041K38358Kreduced by 4%
Total Thread Started3428-4
Total Classes Loaded105059744reduced by 7%

It trades memory footprint with latency by lazy initializing beans.

5.4 Disable Unused Spring Feature

In this step, I will adjust the Spring Boot Application configuration to disable unused features.

application.properties.java

spring.jmx.enabled=false
spring.main.banner-mode=off

After updating the Tomcat thread setting, start the Spring Boot application and capture its memory usage.

jcmd 28564 GC.heap_info

C:\MaryZheng\workspace\sbmemory21g>jcmd 28564 GC.heap_info
28564:
 garbage-first heap   total 98304K, used 52560K [0x0000000704e00000, 0x0000000800000000)
  region size 2048K, 26 young (53248K), 2 survivors (4096K)
 Metaspace       used 38385K, committed 39168K, reserved 1114112K
  class space    used 5388K, committed 5760K, reserved 1048576K

Compare its memory, metaspace, thread, classes loaded to the data captured at step 3.

Basic Spring Boot with Default SettingUpdated Spring Boot Disable JMXComparison Results
Heap Used62525K52560Kreduced by 15%
Metaspace40041K38385Kreduced by 4%
Total Thread Started3428-4
Total Classes Loaded105059765reduced by 7%

6. Container-Friendly Practices

There are several categories based on container-friendly guidelines when configuring JVM options.

  • -XX:+UseContainerSupport to respect container memory limit.
  • Configure memory with -XX:MaxRAMPercentage=75.0 instead of hardcoding -Xmx.
  • Consider G1GC (default in Java 9+) or ZGC for low-latency apps.
  • Adjust GC threads if the container CPU is small
    • -XX:ParallelGCThreads=2
    • -XX:ConcGCThreads=2.
  • Avoid Large Heap Defaults.
  • Control Async/Scheduled thread pools by setting the boundary, e.g. max pool size. max queue capacity, etc.
  • Use smaller docker image
  • Graceful shutdown and set shutdown timeout

7. Other Optimization Techniques

When tuning a Spring Boot web application, there are many optimization techniques beyond just memory and thread settings. They fall into several categories:

  • Database optimization – proper indexes, batch operations, caching, connection pool.
  • Caching – Http caching and application-level caching.
  • Security Optimizations – minimize Spring security filters in the chain.
  • Code-level Best practices – avoid heavy object creation, use DTO instead of entities for API response, minimize reflection, reuse expensive resources, e.g. ObjectMapper.

8. Conclusion

In this example, I created a simple Java Spring Web Application and monitored its memory usage via both VisualVM tool and jcmd, jstat commands. I repeatedly monitor the memory footprint for the same Spring Boot web application program after changing its thread pool, lazy-bean initialization, and disable JMX configuration. Please remember everything comes with price. The reduced memory footprint was traded with throughput and/or latency. Don’t over-optimization the setting.

9. Download

This was an example of a Java Spring Boot Web project.

Download
You can download the full source code of this example here: How to Reduce Spring Boot Memory Usage?

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button