JVM Memory Anatomy: Understanding Heap.
Heap is a memory area where all the objects of your application are stored. It is divided into several areas:
Heap structure:
Young Generation:
Eden Space - new objects are created here
Survivor Space (S0 and S1) - objects that survived the first garbage collection
Old Generation:
Long-lived objects that have survived several garbage collection cycles
Metaspace (replaced PermGen in Java 8+):
Class metadata, constants
How do I configure the Heap size?
# Set the minimum and maximum heap size
java -Xms2g -Xmx4g -jar myapp.jar
# -Xms — initial heap size (2 GB)
# -Xmx — maximum heap size (4 GB)Important rule: Xms and Xmx are better to be made the same in production to avoid changing the heap size during operation.
How to calculate the right size?
To begin with, you can use the formula:
Heap Size = (Peak Live Data Size) × (2-4)Where Peak Live Data Size is the maximum amount of "live" objects after Full GC.
Garbage Collection: select and configure
GC (Garbage Collector) automatically removes unused objects from memory. There are several types of garbage collectors, each with its own characteristics.
Main types of GC:
1. Serial GC (for small applications)
java -XX:+UseSerialGC -jar myapp.jarWhen to use: Single-threaded applications, heap < 100 MB
2. Parallel GC (default in Java 8)
java -XX:+UseParallelGC -jar myapp.jarWhen to use: Applications where throughput is important can tolerate pauses
3. G1GC (recommended for most cases)
java -XX:+UseG1GC -jar myapp.jarWhen to use: Heap > 4 GB, predictable pauses are needed
Setting the target pause time:
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar4. ZGC and Shenandoah (for low-latency applications)
# ZGC (Java 11+, production-ready with Java 15)
java -XX:+UseZGC -jar myapp.jar
# Shenandoah
java -XX:+UseShenandoahGC -jar myapp.jarWhen to use: Critical minimum pauses (< 10 ms), large heap
GC monitoring in logs
Enable detailed GC logs:
java -Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=100m \
-XX:+UseG1GC \
-jar myapp.jarProfiling: finding bottlenecks
Profiling helps you understand where your app is spending time and memory.
1. Use JVisualVM (free)
JVisualVM is included in the JDK and allows you to:
Monitor CPU and memory usage in real time
Take heap dumps
Analyze flows
# Run the application with JMX
java -Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar myapp.jarThen connect via VisualVM to localhost:9010.
2. Heap Dump Analysis
When an OutOfMemoryError occurs, it is useful to get a memory snapshot:
# Automatic dump at OOM
java -XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heapdump.hprof \
-jar myapp.jar
# Or create manually
jmap -dump:live,format=b,file=heap.bin <PID>Analyze the dump in Eclipse MAT or VisualVM, look for:
Objects that take up the most memory
Memory leaks
Unexpectedly large collections
3. Async Profiler (production-ready)
Async-profiler is suitable for production — it does not slow down the application:
# Download
wget https://github.com/jvm-profiling-tools/async-profiler/releases/latest/download/async-profiler-2.9-linux-x64.tar.gz
# Run profiling for 60 seconds
./profiler.sh -d 60 -f flamegraph.html <PID>The result is a flamegraph, where you can see the "hot" methods.
4. Monitoring metrics
Use Micrometer + Prometheus + Grafana:
// Spring Boot automatically exports JVM metrics
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Add to application.properties:
management.endpoints.web.exposure.include=prometheus,health,metrics
management.metrics.export.prometheus.enabled=true
Typical problems and their solutions
Problem 1: Frequent Full GC
Symptoms: The app "freezes" for seconds
Solution:
# Increase heap or configure young generation
java -Xms4g -Xmx4g \
-XX:NewRatio=2 \
-XX:+UseG1GC \
-jar myapp.jarProblem 2: OutOfMemoryError: Java heap space
Solution:
Increase
-XmxFind memory leaks through heap dump
Optimize the code (get rid of unnecessary collections)
Problem 3: OutOfMemoryError: Metaspace
Solution:
# Increase metaspace
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -jar myapp.jarProblem 4: High CPU load due to GC
Solution:
Switch to a more efficient GC (G1, ZGC)
Increase heap
Optimize object creation in code
Checklist for preparation for production
1. Memory settings
java -Xms4g -Xmx4g \ # Fixed heap
-XX:MetaspaceSize=256m \ # Metaspace
-XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC \ # G1 GC
-XX:MaxGCPauseMillis=200 \ # Target pause time
-XX:+HeapDumpOnOutOfMemoryError \ # Dump at OOM
-XX:HeapDumpPath=/var/log/app \
-jar myapp.jar2. GC logging
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=100m3. JMX for monitoring
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.password.file=/etc/jmx.password4. Containerization (Docker)
JVM automatically detects container limits with Java 10+:
FROM openjdk:17-slim
# JVM will automatically see the limits
ENV JAVA_OPTS="-XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-XX:+UseG1GC"
CMD java $JAVA_OPTS -jar app.jar# docker-compose.yml
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 2G5. Monitoring in production
Be sure to keep track of:
Heap usage (used memory)
GC frequency (frequency of assemblies)
GC pause time (duration of pauses)
Thread count (number of streams)
CPU usage
Example of a ready-made configuration
For a typical Spring Boot application:
#!/bin/bash
java -Xms2g -Xmx2g \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=256m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/myapp/heapdump.hprof \
-Xlog:gc*:file=/var/log/myapp/gc.log:time,uptime:filecount=10,filesize=100m \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-jar myapp.jarAdditional tools
GCEasy (gceasy.io) — online GC log analysis
FastThread (fastthread.io) — thread dumps analysis
JProfiler - commercial profiler
YourKit — another great commercial profiler
Arthas — Alibaba's runtime diagnostics tool
Conclusions
JVM optimization is an iterative process. Start by understanding the basic concepts (heap, GC, profiling), use the right tools for diagnostics, and gradually optimize for your specific workload.
Remember: Premature optimization is the root of all evil. Measure first, then optimize!
Was it helpful?
Join Codice - an educational platform for developers!
Here you will find structured courses, practical tasks and up-to-date materials on Java, performance optimization and preparation for production.
And we also have cool Telegram channel with a friendly community, where experienced developers share knowledge, help solve problems, and discuss best practices. Join us!
