공공부하자개발 · 영어 학습 노트
자바
고급모던 자바와 성능0/10 완료
  • 01제네릭과 와일드카드
  • 02멀티스레드와 동기화
  • 03람다식과 함수형 인터페이스
  • 04Stream API와 병렬 처리
  • 05Optional로 NPE 방지
  • 06메서드 활용 패턴 (고급)
  • 07Java 21 모던 문법
  • 08어노테이션·리플렉션·동적 프록시
  • 09CompletableFuture 심화와 가상 스레드 실전
  • 10JVM 메모리·GC·OOM 진단
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 고급 › 10 / 10

JVM 메모리·GC·OOM 진단

힙은 어떻게 생겼고, 무엇이 남고, 터지면 어디를 보는가
섹션 7진행 0 / 10
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 JVM 메모리 지도

text
┌─ 힙 (Heap, -Xmx) ────────────────────────────────┐
│  Young: Eden ──▶ Survivor 0/1     Old (Tenured)   │
│  새 객체는 Eden 에서 태어나고, 살아남으면 Old 로   │
└───────────────────────────────────────────────────┘
┌─ 비힙 ────────────────────────────────────────────┐
│  Metaspace (클래스 정보)   CodeCache (JIT 결과)    │
│  스레드 스택 (-Xss, 스레드마다)                     │
└───────────────────────────────────────────────────┘

-Xmx 는 힙만 제한합니다. 컨테이너 메모리 한도가 1GB 인데 -Xmx1g 를 주면 메타스페이스·스택·네이티브 버퍼가 들어갈 자리가 없어 OS 가 프로세스를 죽입니다. 힙은 컨테이너 한도의 50~75% 로 잡습니다.

옵션 뜻 기본값
-Xmx 최대 힙 물리 메모리의 1/4
-Xms 시작 힙 물리 메모리의 1/64
-Xss 스레드 스택 1MB (플랫폼 스레드)
-XX:MaxMetaspaceSize 메타스페이스 상한 무제한

운영에서는 -Xms 와 -Xmx 를 같게 둡니다. 힙이 커졌다 줄었다 하면서 생기는 Full GC 를 피하기 위해서입니다.

2.2 세대별 가설: 대부분은 태어나자마자 죽습니다

요청 하나를 처리하며 만드는 DTO·문자열·스트림 객체는 응답이 나가면 전부 쓰레기입니다. 이런 객체는 Eden 에서 태어나 첫 Young GC 에 사라집니다. Young GC 는 "살아 있는 것만 복사" 하므로 쓰레기가 많을수록 빠릅니다.

몇 번의 Young GC 를 살아남은 객체는 Old 로 승격됩니다. 캐시, 커넥션 풀, 싱글턴 빈이 여기 삽니다. Old 가 가득 차면 Full GC 가 돌고, 이것이 응답 지연의 주범입니다.

3절 예제 2 에서 1GB 를 할당해도 Young GC 7회, Old GC 0회인 것이 이 가설의 증거입니다.

2.3 도달 가능성: GC 는 "참조가 끊긴 것" 만 치웁니다

GC 루트(스택의 지역 변수, static 필드, 살아 있는 스레드) 에서 참조를 따라갈 수 있으면 살아 있는 객체입니다. 누수는 "쓰지 않지만 도달은 되는" 객체가 쌓이는 것입니다.

누수 패턴 예
만료 없는 static 캐시 static Map<String, Data> CACHE
리스너·콜백 해제 누락 이벤트 등록 후 remove 안 함
ThreadLocal 정리 누락 풀 스레드에 값이 남음
닫지 않은 스트림·커넥션 네이티브 버퍼까지 붙잡음
큰 컬렉션의 subList 원본 전체를 참조

WeakHashMap 이나 WeakReference 는 "다른 곳에서 안 쓰면 치워도 된다" 를 GC 에 알리는 방법입니다. 단, 값이 아니라 키가 약한 참조라는 점에 주의합니다.

2.4 GC 종류와 선택

GC 방식 어울리는 곳
Serial 스레드 1개, 전부 정지 작은 힙, 코어 1~2개, 배치
Parallel 여러 스레드, 전부 정지 처리량 우선 배치
G1 (기본) 영역 단위, 정지 시간 목표 대부분의 서버
ZGC 거의 전부 동시 실행 큰 힙, 10ms 미만 정지 필요

"전부 정지"(Stop-The-World) 는 GC 동안 애플리케이션 스레드가 멈추는 것입니다. Parallel 은 총 GC 시간이 가장 짧지만 한 번 멈출 때 오래 멈춥니다. ZGC 는 멈춤이 1ms 미만이지만 동시 작업에 CPU 를 씁니다. 처리량과 응답 시간 사이의 거래입니다.

Java 21 의 기본은 G1 이고, 웹 서버에는 그대로 두는 것이 정답에 가깝습니다. 야간 배치처럼 응답 시간이 중요하지 않으면 -XX:+UseParallelGC 가 더 빠를 수 있습니다.

2.5 GC 로그 읽기

-Xlog:gc 한 줄이면 됩니다. 운영에서는 파일로 돌립니다.

text
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=20m

한 줄의 뜻입니다.

text
[0.102s][info][gc] GC(9) Pause Full (G1 Compaction Pause) 62M->62M(64M) 2.089ms
  │              │    │                                   │   │    │     └ 걸린 시간
  │              │    │                                   │   │    └ 전체 힙
  │              │    │                                   │   └ GC 후 사용량
  │              │    │                                   └ GC 전 사용량
  │              │    └ 종류: Young / Full / Remark 등
  │              └ GC 일련번호
  └ JVM 시작 후 경과 시간

62M->62M 처럼 GC 를 해도 줄지 않으면 전부 살아 있는 객체입니다. 이 줄이 반복되다 OOM 이 납니다. 반대로 30M->3M 은 건강한 Young GC 입니다.

2.6 OOM 이 났을 때 보는 순서

  1. 메시지를 봅니다. Java heap space 는 힙, Metaspace 는 클래스 로딩, unable to create native thread 는 스레드 수입니다.
  2. GC 로그에서 OOM 직전 몇 분의 추세를 봅니다. 서서히 올라갔으면 누수, 갑자기 뛰었으면 대량 조회입니다.
  3. 힙 덤프를 MAT 나 VisualVM 으로 열어 "Dominator Tree" 에서 가장 큰 객체를 봅니다. 대개 컬렉션 하나가 절반을 차지합니다.

힙 덤프는 OOM 순간에 자동으로 받도록 미리 옵션을 켜 둡니다. 죽은 뒤에는 받을 수 없습니다.

text
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/

살아 있는 프로세스의 덤프는 jcmd <pid> GC.heap_dump 파일명 으로 받습니다. 덤프 중에는 애플리케이션이 멈추므로 운영에서는 트래픽이 적은 시간에 받습니다.

2.7 객체 크기 감각

64비트 JVM 에서 압축 참조가 켜져 있으면(힙 32GB 미만) 객체 헤더 12바이트, 참조 4바이트입니다. 크기는 8바이트 단위로 올림됩니다.

것 크기
int 하나 4 바이트
Integer 하나 16 바이트 (헤더 12 + 값 4)
빈 String 24 + 16 = 40 바이트
"item123" 24 + 24 = 48 바이트
ArrayList<Integer> 100만 개 약 20 MB
int[] 100만 개 4 MB

100만 건을 List<Integer> 로 들면 int[] 의 5배입니다. 배치에서 ID 목록을 들고 다닐 때 원시 배열이나 IntStream 을 쓰는 이유입니다.

핵심 원리
  • 2.1 JVM 메모리 지도
  • 2.2 세대별 가설: 대부분은 태어나자마자 죽습니다
  • 2.3 도달 가능성: GC 는 "참조가 끊긴 것" 만 치웁니다
  • 2.4 GC 종류와 선택
  • 2.5 GC 로그 읽기
  • 2.6 OOM 이 났을 때 보는 순서
  • 2.7 객체 크기 감각
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제