본문으로 건너뛰기
Advertisement

Pro Tips — JVM 튜닝, GC 분석, Tomcat Native

이 챕터에서는 Tomcat 운영 전문가가 실제로 쓰는 JVM 힙 튜닝 기준, GC 로그 분석 방법, 그리고 성능 향상을 위한 Tomcat Native(APR) 설치까지 실무 노하우를 다룹니다.


JVM 힙 메모리 튜닝

기본 원칙

권장 힙 크기 = 가용 메모리 × 0.5 ~ 0.7

예시:
- 4GB RAM → Xms1g -Xmx2g
- 8GB RAM → Xms2g -Xmx4g
- 16GB RAM → Xms4g -Xmx8g
- 32GB RAM → Xms8g -Xmx16g

setenv.sh — 메모리 설정

# $CATALINA_HOME/bin/setenv.sh

# === 힙 메모리 ===
export CATALINA_OPTS="$CATALINA_OPTS -Xms2g -Xmx4g"

# === GC 알고리즘 선택 ===
# Java 17+: G1GC (기본값, 대부분의 경우 적합)
export CATALINA_OPTS="$CATALINA_OPTS -XX:+UseG1GC"

# G1GC 튜닝
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxGCPauseMillis=200" # 목표 최대 Stop-the-World 시간
export CATALINA_OPTS="$CATALINA_OPTS -XX:G1HeapRegionSize=16m" # 힙 영역 크기 (1~32MB)
export CATALINA_OPTS="$CATALINA_OPTS -XX:InitiatingHeapOccupancyPercent=45" # Old GC 트리거 임계값

# === GC 로그 ===
export CATALINA_OPTS="$CATALINA_OPTS \
-Xlog:gc*:file=$CATALINA_HOME/logs/gc.log:time,uptime,pid:filecount=5,filesize=20m"

# === OOM 시 힙 덤프 자동 생성 ===
export CATALINA_OPTS="$CATALINA_OPTS \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=$CATALINA_HOME/logs/heapdump.hprof"

# === OOM 발생 시 자동 재시작 스크립트 ===
export CATALINA_OPTS="$CATALINA_OPTS \
-XX:OnOutOfMemoryError='systemctl restart tomcat'"

# === 서버 모드 ===
export CATALINA_OPTS="$CATALINA_OPTS -server"

# === 타임존 / 인코딩 ===
export JAVA_OPTS="$JAVA_OPTS -Duser.timezone=Asia/Seoul -Dfile.encoding=UTF-8"

GC 알고리즘 선택 가이드

GCJava 버전특징권장 상황
G1GCJava 9+ (기본)균형잡힌 처리량·지연시간대부분의 서버 애플리케이션
ZGCJava 15+ (안정)초저지연 (< 1ms)응답 지연에 민감한 서비스
ShenandoahJava 12+ (OpenJDK)초저지연, 높은 처리량대용량 힙 (> 8GB)
SerialGC모든 버전단일 스레드개발/테스트 환경
ParallelGC모든 버전처리량 최우선배치 처리

ZGC 설정 예시 (응답 시간 민감 서비스)

# ZGC — 매우 낮은 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 로그 분석

GC 로그 형식 이해

# Java 17+ GC 로그 예시 (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

분석 포인트

Pause Young (Normal)    → 정상 Young GC, < 50ms 권장
Pause Young (Concurrent Start) → 동시 마킹 시작 (Old GC 징조)
Pause Full → Full GC! 원인 파악 필요 (힙 크기 부족 or 메모리 누수)

GC 로그 분석 도구

# 1. GCEasy.io (온라인) — GC 로그 파일 업로드
# https://gceasy.io

# 2. GCViewer (오프픈소스)
java -jar gcviewer-1.36.jar /opt/tomcat/latest/logs/gc.log

# 3. 직접 분석 — Full GC 횟수 확인
grep "Pause Full" /opt/tomcat/latest/logs/gc.log | wc -l

# 4. 평균 GC 시간
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) — 네이티브 성능 향상

Tomcat Native 라이브러리는 OpenSSL과 APR(Apache Portable Runtime)을 사용하여 네이티브 레벨에서 SSL 처리와 파일 I/O를 수행합니다.

성능 개선 효과

항목기본 Java NIOTomcat Native (APR)
SSL HandshakeJava JSSEOpenSSL (빠름)
정적 파일 서빙Java File APIsendfile 시스템 콜
연결 처리Java NIOAPR 네이티브 I/O
권장 환경일반 웹앱고트래픽 파일 서빙 + SSL

현대적 관점: Tomcat을 Nginx 뒤에서 백엔드로 사용한다면 SSL은 Nginx가 처리하므로 Tomcat Native의 실질적 이점은 제한적입니다. 직접 SSL을 처리하거나 대용량 파일 서빙이 필요한 경우에 유용합니다.

Ubuntu에서 Tomcat Native 설치

# 의존성 설치
sudo apt install -y libapr1-dev libssl-dev gcc make

# Tomcat Native 소스 다운로드
TOMCAT_VER=10.1.20
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

# 컴파일 및 설치
./configure \
--with-apr=/usr/bin/apr-1-config \
--with-java-home=$JAVA_HOME \
--with-ssl=yes \
--prefix=/opt/tomcat/latest

make && sudo make install

# 설치 확인
ls /opt/tomcat/latest/lib/libtcnative*

setenv.sh에 네이티브 라이브러리 경로 추가

# 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"

설치 확인

# catalina.out에서 APR 로드 메시지 확인
grep "apr" /opt/tomcat/latest/logs/catalina.out
# INFO [...] AprLifecycleListener.init Loaded Apache Tomcat Native library [2.0.7] ...
# INFO [...] AprLifecycleListener.init APR capabilities: IPv6 [true], sendfile [true], ...

커넥션 풀 튜닝 — 데이터베이스 연결

<!-- context.xml 또는 conf/Catalina/localhost/앱.xml -->
<Resource name="jdbc/myDB"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"

driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://db-host:3306/mydb?useSSL=false&amp;serverTimezone=Asia/Seoul&amp;rewriteBatchedStatements=true"
username="appuser"
password="AppP@ssw0rd"

<!-- 크기 -->
initialSize="10"
maxTotal="100"
maxIdle="30"
minIdle="10"

<!-- 타임아웃 -->
maxWaitMillis="30000"
removeAbandoned="true"
removeAbandonedTimeout="60"
logAbandoned="true"

<!-- 연결 유효성 검사 -->
validationQuery="SELECT 1"
testOnBorrow="true"
testWhileIdle="true"
timeBetweenEvictionRunsMillis="30000"
minEvictableIdleTimeMillis="60000"/>

자주 발생하는 문제와 해결책

문제 1: OutOfMemoryError: Java heap space

# 증상
java.lang.OutOfMemoryError: Java heap space

# 원인 진단
# 1. 힙 덤프 분석 (Eclipse MAT)
jmap -dump:format=b,file=/tmp/heapdump.hprof <PID>
# 또는 -XX:+HeapDumpOnOutOfMemoryError 자동 생성된 파일 분석

# 2. 힙 사용량 실시간 확인
jstat -gcutil <PID> 1000 10
# S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
# 0.00 12.34 75.23 85.67 ...

# 해결
# setenv.sh에서 -Xmx 증가
export CATALINA_OPTS="$CATALINA_OPTS -Xms4g -Xmx8g"

문제 2: PermGen / Metaspace OOM

# Java 8+는 PermGen → Metaspace
# 증상
java.lang.OutOfMemoryError: Metaspace

# 해결: Metaspace 최대 크기 설정
export CATALINA_OPTS="$CATALINA_OPTS -XX:MaxMetaspaceSize=512m"

# 클래스 언로딩 활성화
export CATALINA_OPTS="$CATALINA_OPTS -XX:+ClassUnloadingWithConcurrentMark"

문제 3: 스레드 고갈 (Thread Starvation)

# 증상: HTTP 503, catalina.out에 다음 메시지
# WARNING: All available threads (200) are currently busy

# 스레드 덤프 생성
kill -3 <TOMCAT_PID>
# catalina.out에 전체 스레드 상태 출력

# 또는 jstack 사용
jstack <PID> > /tmp/thread-dump.txt

# 분석: BLOCKED 스레드가 많다면 Lock 경합 또는 DB 연결 풀 고갈
grep "BLOCKED" /tmp/thread-dump.txt | wc -l

# 해결: maxThreads 증가 또는 DB 풀 확대

문제 4: 앱 재배포 후 클래스 로더 메모리 누수

# 증상: 재배포 반복 시 Metaspace 점진적 증가
# 원인: 정적 필드에 클래스로더 참조, ThreadLocal 미정리

# 확인: 재배포 경고 메시지
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. This is very likely to create a memory leak.

# 해결
# 1. ThreadLocal.remove() 호출 보장 (finally 블록)
# 2. 애플리케이션 종료 시 스레드 풀 shutdown
# 3. Listener를 이용한 리소스 정리

운영 체크리스트

#!/bin/bash
# Tomcat 헬스체크 스크립트

echo "=== Tomcat 상태 확인 ==="

# 1. 프로세스 확인
if ! pgrep -f "catalina" > /dev/null; then
echo "❌ Tomcat 프로세스 없음"
exit 1
fi

# 2. HTTP 응답 확인
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/)
if [ "$HTTP_CODE" != "200" ]; then
echo "❌ HTTP 응답 이상: $HTTP_CODE"
exit 1
fi

# 3. 힙 사용량 확인
PID=$(pgrep -f catalina)
HEAP_USED=$(jstat -gcutil $PID | tail -1 | awk '{print $4}')
echo "✅ Old Gen 사용률: ${HEAP_USED}%"
if (( $(echo "$HEAP_USED > 85" | bc -l) )); then
echo "⚠️ 힙 사용률 경고: ${HEAP_USED}%"
fi

# 4. Full GC 횟수 확인 (지난 1시간)
FGC_COUNT=$(jstat -gcutil $PID | tail -1 | awk '{print $6}')
echo "✅ Full GC 횟수: $FGC_COUNT"

echo "✅ 모든 체크 통과"

Summary

항목권장 설정
힙 크기-Xms = -Xmx = 가용 RAM × 0.5~0.7
GC 알고리즘Java 17+: G1GC (기본), 저지연 필요 시 ZGC
GC 로그-Xlog:gc*:file=gc.log:filecount=5,filesize=20m
OOM 대응-XX:+HeapDumpOnOutOfMemoryError
Tomcat Native직접 SSL 처리 또는 고트래픽 파일 서빙 시
DB 풀 크기maxTotal = maxThreads × 0.5~0.8
Advertisement