Java 기반 서비스가 느려질 때 가장 먼저 의심해야 하는 영역은 GC다.
특히 Java 7 또는 초기 Java 8 환경에서 CMS(Concurrent Mark Sweep)로 운영되는 레거시 시스템이라면 Young/Old 비율과 Promotion량 제어가 성능의 핵심이다.
이 글은 그러한 환경을 기준으로 작성한 실전 튜닝 가이드다.
(Java 11·17 등 최신 JVM에서는 GC 구조가 완전히 다르므로 이 옵션을 그대로 적용하면 안 된다.)
1. JVM 버전에 따라 반드시 달라지는 전제 사항
✔ PermGen은 Java 7까지만 존재한다 (가장 중요한 기술 포인트)
Java 7 이하에서는 클래스 메타데이터가 PermGen에 저장된다.
하지만 Java 8부터 PermGen은 완전히 제거되었고, Metaspace로 대체되었다.
따라서 다음 옵션은 Java 7 이하에서만 유효하다:
-XX:PermSize
-XX:MaxPermSize
Java 8에서 이 옵션을 넣으면 JVM이 죽지는 않지만 다음과 같은 메시지가 출력되며 옵션이 완전히 무시된다:
warning: ignoring option PermSize=256m; support was removed in 8.0
➡ Java 8 이상에서는 아래 옵션을 사용해야 한다:
-XX:MaxMetaspaceSize=...
즉, PermGen 설정은 오직 Java 7 이하 또는 “Java 8을 Java 7처럼 사용하던 초기 이행기”에서만 의미가 있다.
✔ CMS GC는 Java 14에서 완전히 제거됨
다음 옵션은 Java 8까지는 정상 동작하지만, Java 9부터 deprecated → Java 14에서 삭제되었다.
-XX:+UseConcMarkSweepGC
최신 Java 11/17/21 환경은 기본적으로 G1GC, 선택 시 ZGC, Shenandoah 등을 사용해야 하며 CMS 옵션을 넣으면 JVM이 기동되지 않는다.
이 글이 Java 7/8 CMS 환경을 기준으로 한다는 점을 명확히 하고 넘어가는 이유가 바로 이것이다.
2. Young Generation 구조와 GC 흐름

CMS 환경에서는 Young 영역이 다음과 같이 구성된다:
Eden → Survivor 1 → Survivor 2
- 새 객체는 Eden에 생성
- Minor GC 시 살아남은 객체가 S1/S2로 이동
- 여러 회 생존한 객체만 Old로 승격(Promotion)
✔ Young 영역을 크게 잡으면 생기는 실제 효과
- Promotion 자체가 줄어들어 Old 압박 감소
- Full GC 빈도 극적인 감소
- 대부분의 일시적 객체가 Young에서 사라지므로 안정적인 GC 흐름 확보
즉, “Young 확대”는 CMS 환경에서 Full GC를 줄이는 가장 직접적인 방법이다.
3. Young/Old 비율 1:2가 기본으로 존재하는 이유
대부분의 문서에서는 Young : Old = 1 : 2 비율을 기본 권장한다.
그 이유는 단순하다:
- Young이 지나치게 작으면 → Promotion 급증 → Old 빠르게 소진 → Full GC 증가
- Old가 지나치게 작으면 → 역시 Full GC 증가
- 1:2 비율이 “가장 무난하고 안정적인 패턴”을 만든다
📌 정상 JVM은 Minor GC가 압도적으로 많아야 한다.
Minor GC 수천~수만 회 / Full GC 하루 1~5회 → 정상 패턴.
4. SurvivorRatio = 8 기준 메모리 배치
SurvivorRatio=8이면 Young은 10개 단위로 쪼개지며 비율은 다음과 같다:
| 영역 | 비율 | 계산식 |
|---|---|---|
| Eden | 8/10 | NewSize × 0.8 |
| Survivor 1 | 1/10 | NewSize × 0.1 |
| Survivor 2 | 1/10 | NewSize × 0.1 |
따라서 글에서 설명한 Eden ≈ 80%, Survivor ≈ 10%는 정확한 내용이다.
5. “기본 비율(1:2)”을 깬 Young 중심 튜닝 사례
(가장 혼동을 줄 수 있는 부분을 명확하게 정리한 영역)
섹션 2에서는 Young : Old = 1 : 2를 기본 권장이라고 설명했다.
그런데 실제 적용 예제에서는 Young이 Old보다 더 크다:
Heap = 1408MB
NewSize = 796MB (Young)
Old = 612MB
즉 Young > Old (약 1.3 : 1)
이 비율은 “틀린 설정”이 아니고, 특정 서비스 패턴을 해결하기 위한 의도된 튜닝이다.
✔ 왜 Young을 Old보다 크게 했는가?
해당 서비스는 다음 특성을 가지고 있었다:
- 요청당 단기 객체 폭발적 생성
- 대부분 수 ms~수 초 내 사라지는 휘발성 객체
- Promotion이 많이 발생하면 Old 영역이 금방 가득 차서 Full GC 반복
따라서 튜닝 목표는 다음이었다:
👉 Old로 승격되는 객체를 거의 “0에 가깝게” 만든다
👉 대부분 Eden에서 Minor GC로 자연 소멸되게 한다
👉 Old는 Promotion을 최소화한 “완충 영역” 정도로만 유지한다
→ 이 경우 Young을 Old보다 크게 잡는 것이 가장 적합한 전략이다.
즉,
“권장 비율은 1:2지만, 모든 환경에 동일하게 적용하는 것은 아니다.
서비스 패턴에 따라 Young을 더 크게 잡는 것이 오히려 정답일 수 있다.”
이 한 문장을 넣어주면 글의 논리적 일관성이 완벽하게 정리된다.
6. 실제 운영에서 사용된 JVM 메모리 구성
아래 값은 당시 트래픽 패턴을 기반으로 도출된 최종 튜닝값이다:
✔ 전체 Heap: 1408MB
- NewSize = 796MB
- Eden ≈ 636MB
- Survivor each ≈ 80MB
- Old = 612MB
- PermGen = 256MB (Java 7 기준)
CMS 기반에서 단기 객체 생성량이 많은 서비스에는 매우 합리적인 조합이다.
7. JVM 옵션 Before / After
✔ 기존 옵션
-Xms1024m -Xmx1024m
-XX:NewSize=512m -XX:MaxNewSize=512m
-XX:PermSize=512m -XX:MaxPermSize=512m
-XX:+DisableExplicitGC
-XX:-PrintGC -XX:-PrintGCDetails -XX:-PrintGCTimeStamps
-XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseParNewGC
✔ 튜닝 후 적용 옵션 (Java 7 기준)
-Xms1408m -Xmx1408m
-XX:NewSize=796m -XX:MaxNewSize=796m
-XX:PermSize=256m -XX:MaxPermSize=256m
-XX:+DisableExplicitGC
-XX:-PrintGC -XX:-PrintGCDetails -XX:-PrintGCTimeStamps
-XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseParNewGC
핵심 변화:
- Young 확대로 Promotion 억제
- Old는 필요한 최소 범위만 확보
- PermGen은 Java 7 기준으로 적정 수준으로 축소
8. Full GC 해석 기준
- 레거시 모놀리식·배치 중심 서비스 → Full GC 3~5초 허용 가능
- 최신 API 서버·MSA → Full GC 1초 미만을 목표로 하는 경우도 있음
Full GC time보다 더 중요한 것은:
👉 Full GC가 얼마나 자주 발생하느냐
(Minor 수천 회 / Full 한 자릿수 → 정상)
9. GC가 문제처럼 보일 뿐, 실제 원인은 애플리케이션 코드일 수 있다
Full GC 증가의 80%는 GC 튜닝 문제가 아니라 코드 문제에서 나온다.
대표적 사례:
- 세션에 과도한 객체 저장
- 대용량 쿼리 결과를 메모리에 그대로 보관
- JDBC Connection leak
- 비효율적 캐싱 전략
- 세션/객체 재사용 불가 구조
이러한 문제를 해결하지 않으면 GC 튜닝만으로는 안정화가 불가능하다.
10. 결론 — 이 글이 말하고 싶은 핵심
- 이 가이드는 Java 7/8 CMS 환경을 기준으로 한다.
- PermGen 설정은 Java 7까지만 유효하며, Java 8에서는 무시된다.
→ Java 8 이상은 Metaspace 옵션 사용 - Young : Old = 1 : 2가 기본 공식이지만,
업무 특성에 따라 Young을 Old보다 크게 잡는 것이 오히려 정답일 수 있다. - Young 확대는 Promotion 억제 → Full GC 감소에 가장 직접적 효과
- GC 문제의 근본 원인은 애플리케이션 레이어에 존재하는 경우가 많음
🛠 마지막 수정일: 2025.12.12
ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. All rights reserved. Unauthorized copying or redistribution of the text and images is prohibited. When sharing, please include the original source link.
💡 도움이 필요하신가요?
Zabbix, Kubernetes, 그리고 다양한 오픈소스 인프라 환경에 대한 구축, 운영, 최적화, 장애 분석,
광고 및 협업 제안이 필요하다면 언제든 편하게 연락 주세요.
📧 Contact: jikimy75@gmail.com
💼 Service: 구축 대행 | 성능 튜닝 | 장애 분석 컨설팅
📖 E-BooK [PDF] 전자책 (Gumroad):
Zabbix 엔터프라이즈 최적화 핸드북
블로그에서 다룬 Zabbix 관련 글들을 기반으로 실무 중심의 지침서로 재구성했습니다.
운영 환경에서 바로 적용할 수 있는 최적화·트러블슈팅 노하우까지 모두 포함되어 있습니다.
💡 Need Professional Support?
If you need deployment, optimization, or troubleshooting support for Zabbix, Kubernetes,
or any other open-source infrastructure in your production environment, or if you are interested in
sponsorships, ads, or technical collaboration, feel free to contact me anytime.
📧 Email: jikimy75@gmail.com
💼 Services: Deployment Support | Performance Tuning | Incident Analysis Consulting
📖 PDF eBook (Gumroad):
Zabbix Enterprise Optimization Handbook
A single, production-ready PDF that compiles my in-depth Zabbix and Kubernetes monitoring guides.
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.