Pro Tips — JVM Tuning, GC Analysis, Tomcat Native
This chapter covers real-world techniques used by Tomcat operations experts: JVM heap sizing guidelines, GC log analysis, and Tomcat Native (APR) installation for performance gains.
JVM Heap Memory Tuning
Basic Principle
Recommended heap size = available memory × 0.5 ~ 0.7
Examples:
- 4GB RAM → -Xms1g -Xmx2g
- 8GB RAM → -Xms2g -Xmx4g
- 16GB RAM → -Xms4g -Xmx8g
- 32GB RAM → -Xms8g -Xmx16g
setenv.sh — Memory Configuration
# $CATALINA_HOME/bin/setenv.sh
# === Heap memory ===
export CATALINA_OPTS="$CATALINA_OPTS -Xms2g -Xmx4g"
# === GC algorithm ===
# Java 17+: G1GC (default, suitable for most cases)
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"
# G1GC tuning
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=200"
export CATALINA_OPTS="$CATALINA_OPTS -XX:G1HeapRegionSize=16m"
export CATALINA_OPTS="$CATALINA_OPTS -XX:InitiatingHeapOccupancyPercent=45"
# === GC log ===
export CATALINA_OPTS="$CATALINA_OPTS \
-Xlog:gc*:file=$CATALINA_HOME/logs/gc.log:time,uptime,pid:filecount=5,filesize=20m"
# === Auto heap dump on OOM ===
export CATALINA_OPTS="$CATALINA_OPTS \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=$CATALINA_HOME/logs/heapdump.hprof"
# === Auto restart on OOM ===
export CATALINA_OPTS="$CATALINA_OPTS \
-XX:OnOutOfMemoryError='systemctl restart tomcat'"
# === Server mode ===
export CATALINA_OPTS="$CATALINA_OPTS -server"
# === Timezone / Encoding ===
export JAVA_OPTS="$JAVA_OPTS -Duser.timezone=Asia/Seoul -Dfile.encoding=UTF-8"
GC Algorithm Selection Guide
| GC | Java Version | Characteristics | Best For |
|---|---|---|---|
| G1GC | Java 9+ (default) | Balanced throughput & latency | Most server applications |
| ZGC | Java 15+ (stable) | Ultra-low latency (< 1ms) | Latency-sensitive services |
| Shenandoah | Java 12+ (OpenJDK) | Ultra-low latency, high throughput | Large heaps (> 8GB) |
| SerialGC | All versions | Single-threaded | Development/testing |
| ParallelGC | All versions | Maximum throughput | Batch processing |
ZGC Configuration Example (Latency-Sensitive)
# ZGC — very low Stop-the-World (< 1ms)
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseZGC"
export CATALINA_OPTS="$CATALINA_OPTS -XX:SoftMaxHeapSize=6g"
export CATALINA_OPTS="$CATALINA_OPTS -Xmx8g"
GC Log Analysis
Understanding GC Log Format
# Java 17+ GC log example (G1GC)
[0.123s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 256M->100M(512M) 8.234ms
[0.456s][info][gc] GC(1) Pause Young (Concurrent Start) (G1 Humongous Allocation) 400M->200M(512M) 15.678ms
[1.234s][info][gc] GC(2) Pause Full (G1 Compaction Pause) 500M->300M(512M) 1250.123ms
Analysis Points
Pause Young (Normal) → Normal Young GC, < 50ms recommended
Pause Young (Concurrent Start) → Concurrent marking starts (Old GC sign)
Pause Full → Full GC! Investigate (insufficient heap or memory leak)
GC Log Analysis Tools
# 1. GCEasy.io (online) — upload GC log file
# 2. GCViewer (open source)
java -jar gcviewer-1.36.jar /opt/tomcat/latest/logs/gc.log
# 3. Count Full GC occurrences
grep "Pause Full" /opt/tomcat/latest/logs/gc.log | wc -l
# 4. Average GC pause time
grep "Pause Young" /opt/tomcat/latest/logs/gc.log | \
awk -F'ms' '{print $(NF-1)}' | \
awk '{sum+=$1; cnt++} END {print "Avg:", sum/cnt "ms"}'
Tomcat Native (APR) — Native Performance
The Tomcat Native library uses OpenSSL and APR (Apache Portable Runtime) to perform SSL processing and file I/O at the native level.
Performance Improvements
| Item | Default Java NIO | Tomcat Native (APR) |
|---|---|---|
| SSL Handshake | Java JSSE | OpenSSL (faster) |
| Static file serving | Java File API | sendfile system call |
| Connection handling | Java NIO | APR native I/O |
| Recommended for | General web apps | High-traffic file serving + SSL |
Modern perspective: If Tomcat runs behind Nginx as a backend, Nginx handles SSL, so Tomcat Native's practical benefit is limited. It's most useful when Tomcat handles SSL directly or serves large files.
Installing Tomcat Native on Ubuntu
# Install dependencies
sudo apt install -y libapr1-dev libssl-dev gcc make
# Download Tomcat Native source
cd /tmp
wget https://dlcdn.apache.org/tomcat/tomcat-connectors/native/2.0.7/source/tomcat-native-2.0.7-src.tar.gz
tar -xzf tomcat-native-2.0.7-src.tar.gz
cd tomcat-native-2.0.7-src/native
# Compile and install
./configure \
--with-apr=/usr/bin/apr-1-config \
--with-java-home=$JAVA_HOME \
--with-ssl=yes \
--prefix=/opt/tomcat/latest
make && sudo make install
# Verify installation
ls /opt/tomcat/latest/lib/libtcnative*
Add Native Library Path to setenv.sh
export LD_LIBRARY_PATH="/opt/tomcat/latest/lib:$LD_LIBRARY_PATH"
export CATALINA_OPTS="$CATALINA_OPTS \
-Djava.library.path=/opt/tomcat/latest/lib"
Common Issues and Solutions
Issue 1: OutOfMemoryError: Java heap space
# Symptom
java.lang.OutOfMemoryError: Java heap space
# Diagnosis
# Check heap usage in real-time
jstat -gcutil <PID> 1000 10
# Analyze heap dump with Eclipse MAT
jmap -dump:format=b,file=/tmp/heapdump.hprof <PID>
# Fix: increase -Xmx in setenv.sh
export CATALINA_OPTS="$CATALINA_OPTS -Xms4g -Xmx8g"
Issue 2: Metaspace OOM
# Symptom
java.lang.OutOfMemoryError: Metaspace
# Fix: set max Metaspace size
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=512m"
# Enable class unloading
export CATALINA_OPTS="$CATALINA_OPTS -XX:+ClassUnloadingWithConcurrentMark"
Issue 3: Thread Starvation
# Symptom: HTTP 503, catalina.out shows:
# WARNING: All available threads (200) are currently busy
# Generate thread dump
kill -3 <TOMCAT_PID>
# Or use jstack
jstack <PID> > /tmp/thread-dump.txt
# Analyze: many BLOCKED threads indicate lock contention or DB pool exhaustion
grep "BLOCKED" /tmp/thread-dump.txt | wc -l
# Fix: increase maxThreads or DB pool size
Issue 4: ClassLoader Memory Leak After Redeployment
# Symptom: Metaspace grows progressively after repeated redeployments
# Check warning messages
grep "The web application \[" /opt/tomcat/latest/logs/catalina.out
# WARNING: The web application [...] appears to have started a thread
# named [xxx] but has failed to stop it.
# Fix:
# 1. Ensure ThreadLocal.remove() in finally blocks
# 2. Shutdown thread pools on application stop
# 3. Clean up resources in ServletContextListener
Operations Health Check Script
#!/bin/bash
echo "=== Tomcat Health Check ==="
# 1. Process check
if ! pgrep -f "catalina" > /dev/null; then
echo "❌ Tomcat process not running"
exit 1
fi
# 2. HTTP response check
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/)
if [ "$HTTP_CODE" != "200" ]; then
echo "❌ Abnormal HTTP response: $HTTP_CODE"
exit 1
fi
# 3. Heap usage check
PID=$(pgrep -f catalina)
HEAP_USED=$(jstat -gcutil $PID | tail -1 | awk '{print $4}')
echo "✅ Old Gen usage: ${HEAP_USED}%"
if (( $(echo "$HEAP_USED > 85" | bc -l) )); then
echo "⚠️ Heap usage warning: ${HEAP_USED}%"
fi
echo "✅ All checks passed"
Summary
| Item | Recommended Setting |
|---|---|
| Heap size | -Xms = -Xmx = available RAM × 0.5~0.7 |
| GC algorithm | Java 17+: G1GC (default), ZGC for low-latency |
| GC log | -Xlog:gc*:file=gc.log:filecount=5,filesize=20m |
| OOM handling | -XX:+HeapDumpOnOutOfMemoryError |
| Tomcat Native | When handling SSL directly or high-traffic file serving |
| DB pool size | maxTotal = maxThreads × 0.5~0.8 |