[Java 7/8 · CMS GC 기준] Tomcat GC 튜닝 완전 이해 — Young/Old 비율, Full GC 최소화 전략, 실제 운영값 분석

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개 단위로 쪼개지며 비율은 다음과 같다:

영역비율계산식
Eden8/10NewSize × 0.8
Survivor 11/10NewSize × 0.1
Survivor 21/10NewSize × 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

핵심 변화:

  1. Young 확대로 Promotion 억제
  2. Old는 필요한 최소 범위만 확보
  3. 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. 결론 — 이 글이 말하고 싶은 핵심

  1. 이 가이드는 Java 7/8 CMS 환경을 기준으로 한다.
  2. PermGen 설정은 Java 7까지만 유효하며, Java 8에서는 무시된다.
    → Java 8 이상은 Metaspace 옵션 사용
  3. Young : Old = 1 : 2가 기본 공식이지만,
    업무 특성에 따라 Young을 Old보다 크게 잡는 것이 오히려 정답일 수 있다.
  4. Young 확대는 Promotion 억제 → Full GC 감소에 가장 직접적 효과
  5. GC 문제의 근본 원인은 애플리케이션 레이어에 존재하는 경우가 많음

🛠 마지막 수정일: 2025.12.12

ⓒ 2025 엉뚱한 녀석의 블로그 [quirky guy's Blog]. 본문 및 이미지를 무단 복제·배포할 수 없습니다. 공유 시 반드시 원문 링크를 명시해 주세요.
ⓒ 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.