채용 공고에서 말하는 "JVM 커스터마이징 경험"은 주로 다음을 의미합니다:
실무에서는 프로덕션 환경의 트래픽 패턴, 메모리 사용 패턴, 응답 시간 요구사항을 분석하여 JVM 옵션을 조정함으로써 비용 절감(더 적은 서버로 동일 처리량 달성)과 사용자 경험 개선(낮은 레이턴시)을 동시에 달성합니다.
JVM (Java Virtual Machine)
자바 바이트코드를 실행하는 가상 머신입니다. JVM은 메모리 관리(GC), JIT 컴파일, 스레드 관리 등을 담당하며, 이러한 동작은 JVM 옵션으로 제어할 수 있습니다.
Garbage Collection (GC)
사용하지 않는 객체를 자동으로 메모리에서 해제하는 프로세스입니다. GC가 실행되는 동안 애플리케이션 스레드가 멈추는 "Stop-The-World" 현상이 발생하므로, GC 튜닝은 성능 최적화의 핵심입니다.
Heap 메모리
객체가 할당되는 메모리 영역으로, Young Generation(Eden, Survivor)과 Old Generation으로 나뉩니다. 힙 크기가 너무 작으면 GC가 빈번하게 발생하고, 너무 크면 GC 한 번의 시간이 길어집니다.
JIT (Just-In-Time) 컴파일러
런타임에 바이트코드를 네이티브 코드로 컴파일하여 실행 속도를 높입니다. 자주 실행되는 "핫스팟" 코드를 최적화합니다.
실무에서 JVM을 커스터마이징하는 주요 상황
1. 높은 트래픽 처리 시 GC로 인한 응답 지연
문제 상황: e커머스 사이트에서 프로모션 시간대에 응답이 느려지고, 모니터링 결과 GC가 1초 이상 발생하여 사용자 경험이 저하됨.
# 기본 설정 (문제 상황)
java -jar app.jar
# 기본 GC(Serial/Parallel)로 인해 긴 STW 발생
해결 방법: G1GC로 변경하고 목표 응답 시간 설정
# G1GC 튜닝
java -Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:G1HeapRegionSize=16m \
-XX:InitiatingHeapOccupancyPercent=45 \
-jar app.jar
# 결과: GC 일시 정지 시간이 200ms 이하로 감소
2. 메모리 누수로 인한 빈번한 Full GC
문제 상황: 애플리케이션이 며칠 실행되면 메모리가 계속 증가하고, 결국 Full GC가 빈번하게 발생하여 서비스가 느려짐.
해결 방법: 힙 덤프 분석 후 메모리 누수 수정, 그리고 적절한 힙 크기 설정
# 힙 덤프 활성화 및 메모리 설정
java -Xms8g -Xmx8g \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heap_dump.hprof \
-XX:+UseG1GC \
-jar app.jar
# 덤프 분석 도구: Eclipse MAT, VisualVM
# 누수 원인 발견 → 코드 수정 → 재배포
3. 마이크로서비스 환경에서 컨테이너 메모리 제한
문제 상황: Kubernetes에서 Pod 메모리를 2GB로 제한했는데, JVM이 이를 인지하지 못하고 더 많은 메모리를 할당하려다 OOMKilled됨.
해결 방법: 컨테이너 인식 옵션 활성화 및 명시적 메모리 설정
# Kubernetes 환경 JVM 설정
java -XX:+UseContainerSupport \
-XX:MaxRAMPercentage=75.0 \
-Xms1536m -Xmx1536m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=100 \
-jar app.jar
# 컨테이너 메모리 2GB 중 75%인 1.5GB를 JVM 힙으로 할당
# 나머지는 네이티브 메모리, 메타스페이스 등에 사용
4. 배치 작업 최적화 - 처리량 우선
문제 상황: 대량 데이터 처리 배치 작업이 너무 오래 걸림. 응답 시간보다 처리량이 중요한 상황.
해결 방법: Parallel GC 사용 및 큰 힙 크기 설정
# 배치 작업 최적화
java -Xms16g -Xmx16g \
-XX:+UseParallelGC \
-XX:ParallelGCThreads=8 \
-XX:+UseAdaptiveSizePolicy \
-jar batch-app.jar
# Parallel GC: 처리량 극대화, 긴 일시 정지 허용
# 배치 작업은 사용자 대기가 없으므로 적합
5. 저지연(Low Latency) 금융 시스템
문제 상황: 주식 거래 시스템에서 밀리초 단위 지연도 치명적. GC로 인한 일시 정지를 최소화해야 함.
해결 방법: ZGC 또는 Shenandoah GC 사용
# ZGC (Java 15+)
java -Xms32g -Xmx32g \
-XX:+UseZGC \
-XX:ZCollectionInterval=5 \
-XX:+UnlockExperimentalVMOptions \
-jar trading-app.jar
# ZGC: 10ms 이하 일시 정지 시간 보장
# 대규모 힙(TB급)에서도 일관된 저지연 유지
주요 JVM 옵션 카테고리
| 카테고리 | 주요 옵션 | 설명 |
|---|---|---|
| 힙 메모리 | -Xms, -Xmx | 초기 힙 크기, 최대 힙 크기 (보통 동일하게 설정) |
| GC 선택 | -XX:+UseG1GC -XX:+UseZGC -XX:+UseParallelGC |
G1GC: 균형 잡힌 성능 ZGC: 초저지연 Parallel: 처리량 우선 |
| GC 튜닝 | -XX:MaxGCPauseMillis -XX:G1HeapRegionSize |
목표 일시 정지 시간 G1 리전 크기 설정 |
| 메타스페이스 | -XX:MetaspaceSize -XX:MaxMetaspaceSize |
클래스 메타데이터 저장 공간 (Java 8+) |
| 진단/모니터링 | -XX:+PrintGCDetails -Xlog:gc* |
GC 로그 출력 (Java 8 / Java 9+) |
| JIT 컴파일 | -XX:CompileThreshold | 메서드 컴파일 임계값 (기본 10000) |
실무 튜닝 프로세스
JVM 커스터마이징은 힙 메모리 크기, GC 알고리즘 선택, GC 파라미터 튜닝의 3가지 축으로 이루어지며, 애플리케이션의 특성(웹 서비스, 배치, 저지연 시스템)에 따라 다른 전략을 취합니다.
| GC 종류 | 일시 정지 시간 | 처리량 | 적합한 용도 | 주요 옵션 |
|---|---|---|---|---|
| Serial GC | 김 (단일 스레드) | 낮음 | 단일 CPU, 작은 힙 (<100MB) | -XX:+UseSerialGC |
| Parallel GC | 길지만 예측 가능 | 높음 | 배치 처리, 대량 데이터 | -XX:+UseParallelGC |
| G1GC | 중간 (200ms 목표) | 중상 | 대부분의 웹 서비스 (기본 권장) | -XX:+UseG1GC -XX:MaxGCPauseMillis=200 |
| ZGC | 매우 짧음 (<10ms) | 중 | 저지연 요구사항, 대용량 힙 | -XX:+UseZGC (Java 15+) |
| Shenandoah | 매우 짧음 (<10ms) | 중 | 저지연, Red Hat 환경 | -XX:+UseShenandoahGC (OpenJDK) |
1. GC 로그 반드시 활성화하기
# Java 8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/gc.log
# Java 9+
-Xlog:gc*:file=/var/log/gc.log:time,level,tags
GC 로그는 성능 이슈 트러블슈팅의 첫 출발점입니다. GCeasy, GCViewer 같은 도구로 시각화할 수 있습니다.
2. 힙 크기는 시스템 메모리의 70-75%로
컨테이너 환경에서는 네이티브 메모리(스레드 스택, Direct ByteBuffer, Metaspace 등)를 위한 여유 공간이 필요합니다. 전체를 JVM 힙에 할당하면 OOM 발생 위험이 있습니다.
3. Metaspace 크기 제한하기
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
무제한으로 두면 클래스 로딩이 많은 애플리케이션(Spring Boot + 많은 라이브러리)에서 메모리 누수처럼 보일 수 있습니다.
4. 프로덕션 배포 전 부하 테스트 필수
JVM 옵션 변경 후 반드시 실제 트래픽을 시뮬레이션한 부하 테스트를 진행하세요. 예상치 못한 GC 패턴이 나타날 수 있습니다.
5. 모니터링 도구 활용