728x90
반응형
Java 애플리케이션이 갑자기 멈추고 CPU가 치솟는 순간 대부분의 범인은 Full GC다. Full GC는 Young 영역이 아니라 Old 영역(또는 Metaspace·PermGen 포함 전체 힙) 을 정리하는, 가장 무거운 GC 작업이다. 아래 원인들을 알면 왜 Full GC가 터지는지 금방 파악할 수 있다.
1. Old 영역(Heap Old Generation) 메모리 부족
가장 흔한 이유.
- 객체가 Young → Old로 계속 승격(tenured)되며 Old 영역이 가득 찰 때
- GC가 Old 객체들을 치워도 빈 공간이 충분히 확보되지 않을 때
- 메모리 누수(Leaking 객체)로 Old에 객체가 계속 쌓일 때
대표 상황
- 캐시를 너무 크게 사용
- 대용량 리스트/맵을 오래 유지
- static 컬렉션에 데이터 계속 추가
- 스레드 로컬(ThreadLocal) 누수
2. Young GC(=Minor GC)로 해결이 안 되는 경우
Young에서 Survivor로 이동 → 다시 Old로 이동 → Old가 빠르게 증가.
증상
- API 응답은 처음엔 빠르지만 점점 느려짐
- 힙 Old 영역 사용량이 꾸준히 상승 곡선을 그림
3. PermGen / Metaspace 부족 (클래스 메타데이터 영역)
Java 8 이전: PermGen
Java 8 이후: Metaspace
원인 예시
- 동적 클래스 로딩 많음 (예: Spring 프록시, 리플렉션, Groovy, Nashorn, JSP 재컴파일)
- OOM: Metaspace 발생 직전 Full GC 반복
4. System.gc() 호출
가장 의외이지만 매우 강력한 원인.
- 개발 코드 또는 라이브러리에서 System.gc() 호출
- 서버에 따라 JVM 옵션 -XX:+DisableExplicitGC 로 막지 않으면 Full GC 강제 실행됨
5. CMS / G1 등 특정 GC 알고리즘 특성
CMS
- Concurrent Mode Failure → Full GC 강제
- Old compaction이 불가하여 조각난 메모리(fragmentation) 해결 못함 → Full GC 발생
G1 GC
- Mixed GC로 Old 정리 실패 시
- Humongous Object가 많을 때
6. 대용량 객체 생성 (Humongous Objects)
예:
- 1MB 이상의 배열
- 대형 JSON, XML 파싱
- Huge List 생성
대용량 객체는 단번에 Old 영역에 배치될 수 있어 Full GC를 유발한다.
7. Native Memory 부족
Java Heap 문제처럼 보이지만 실제 원인은 Native Memory 부족 → JVM이 Full GC를 유발해 메모리 회수 시도.
원인 예:
- DirectByteBuffer 누수
- Netty / nio buffer
- Thread 너무 많이 생성
8. Heap이 과도하게 작게 설정됨
Heap 자체가 부족하면 정상적인 애플리케이션도 Full GC를 반복한다.
예:
-Xmx512m
처럼 너무 작게 설정됐을 때.
9. Stop-The-World(동시성 문제)로 인한 GC 지연 → Full GC 승격
GC 스레드 부족, CPU 포화로 인해 GC 타이밍을 놓치면 Full GC로 이어진다.
요약
Full GC 발생 3대 핵심 원인
- Old 영역 부족
- Metaspace 부족
- System.gc() 강제 호출
그리고 부가적인 메모리 누수, GC 알고리즘 특성, 대용량 객체 생성 등이 누적되면서 Full GC를 만든다.
728x90
반응형