JVM(Java Virtual Machine)에서 heap 메모리는 자바 애플리케이션이 런타임 시 동적으로 생성하는 객체들이 저장되는 영역입니다. 즉, new 키워드를 사용해서 생성하는 대부분의 객체들이 이 영역에 저장됩니다.
📌 Heap 메모리 구성
Heap은 크게 다음 두 영역으로 나뉩니다:
- Young Generation (Young Gen): 새로 생성된 객체가 저장되는 곳.
- Eden: 대부분의 새 객체가 여기에 생성됨.
- Survivor: Eden에서 살아남은 객체들이 이동.
- Old Generation (Tenured Gen): Young Gen을 거쳐 오래 살아남은 객체들이 이동하는 공간.
Garbage Collector(GC)는 이 heap 영역의 객체들을 주기적으로 검사해서 더 이상 참조되지 않는 객체는 메모리에서 제거합니다.
💣 Memory Leak 이란?
자바에서는 개발자가 직접 메모리를 해제하지 않기 때문에 메모리 누수가 없다고 오해하기 쉬운데, 참조는 남아있지만 실제로 더 이상 사용하지 않는 객체가 GC의 대상이 되지 못할 때 메모리 누수가 발생합니다.
✅ 메모리 누수를 막기 위한 개발자의 역할
-
불필요한 참조 제거
- 더 이상 필요 없는 객체는 참조를
null로 설정하거나 컬렉션에서 제거해야 GC가 수거할 수 있습니다. - 예:
List,Map,Set등에 오래된 데이터를 계속 담아두는 경우.
- 더 이상 필요 없는 객체는 참조를
-
Static 변수 관리
- Static은 클래스 로딩 시 생성되어 애플리케이션 종료 전까지 유지됩니다.
- 필요 이상으로 많은 데이터를 static으로 들고 있으면 GC 대상이 되지 않음.
-
리스너나 콜백 해제
- 등록한 이벤트 리스너, 콜백 등을 적절한 시점에 해제하지 않으면 객체가 계속 참조됨.
-
WeakReference 사용 고려
- 캐시처럼 잠깐만 데이터를 보관하고 싶은 경우
WeakReference,SoftReference등을 사용해 GC 대상이 될 수 있게 설계.
- 캐시처럼 잠깐만 데이터를 보관하고 싶은 경우
-
GC 로그 및 Heap Dump 분석
jvisualvm,jconsole,MAT(Memory Analyzer Tool)등을 통해 객체가 어디서 얼마나 생성되고 남아있는지 확인.
-
Finalizer, Cleaner 피하기
finalize()는 예측이 어렵고 GC를 지연시킬 수 있음. 가능하면AutoCloseable인터페이스와 try-with-resources 구문을 사용할 것.
-
ThreadLocal 사용 주의
- ThreadLocal은 잘못 관리하면 해당 Thread가 종료될 때까지 객체가 해제되지 않음.
- 사용 후 반드시
remove()호출할 것.
실전에서 어떻게 메모리 누수가 생기고, 우리가 어떻게 확인하고 해결할 수 있는지 예제
🧪 1. 예제 코드: 메모리 누수가 발생하는 경우
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakExample {
private static List<Object> leakedList = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
while (true) {
// 객체를 계속 생성해서 static 리스트에 넣는다
leakedList.add(new byte[1024 * 1024]); // 1MB
Thread.sleep(100); // 조금씩 천천히 누수되게
}
}
}
🔥 문제
leakedList는 static 변수라 GC의 대상이 되지 않고,- 내부에 계속 새로운 객체를 추가하므로 Heap 메모리가 점점 차오름.
- 결국
OutOfMemoryError발생.
🛠 2. 메모리 누수 확인 방법 (Heap Dump)
🔍 JVM 옵션 추가
프로그램 실행 시 다음 옵션을 넣으면 OOM 발생 시 Heap Dump 파일이 생성됨:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof
🔧 분석 툴: Eclipse MAT (Memory Analyzer Tool)
- Eclipse MAT 다운로드
heapdump.hprof파일 열기- “Leak Suspects Report” 실행
- 의심되는 객체/경로 확인
- 예:
leakedList가 수많은byte[]객체를 붙잡고 있는 상황 확인 가능
- 예:
✅ 3. 개선된 코드 예시
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakFixed {
public static void main(String[] args) throws InterruptedException {
List<Object> tempList = new ArrayList<>();
while (true) {
tempList.add(new byte[1024 * 1024]); // 1MB
if (tempList.size() > 10) {
tempList.clear(); // 참조 끊기
}
Thread.sleep(100);
}
}
}
이렇게 하면 참조가 사라지고, GC가 해당 객체를 수거할 수 있어서 OOM을 방지할 수 있습니다.
GC 튜닝 전략
좋지! GC 튜닝은 JVM 성능 최적화의 핵심 중 하나야. 튜닝은 사용 중인 Garbage Collector 종류, 메모리 구조, 그리고 애플리케이션의 특성에 따라 달라지는데, 아래 내용을 바탕으로 이해하고 튜닝 전략을 세우면 좋아.
🧠 GC 튜닝의 핵심 목표
- Stop-The-World(STW) 시간을 최소화
- GC 횟수/빈도 최적화
- Throughput (총 처리량) 극대화
- Latency (응답 지연) 최소화
🔧 GC 종류 (Java 11+ 기준)
| GC 종류 | 특징 |
|---|---|
| Serial GC | 단일 스레드, 소규모 앱에 적합 (-XX:+UseSerialGC) |
| Parallel GC | 멀티 스레드, 처리량 위주, 기본값 (-XX:+UseParallelGC) |
| G1 GC | 적절한 latency + throughput (-XX:+UseG1GC) |
| ZGC | 매우 낮은 지연 시간, 대용량 Heap에 적합 (-XX:+UseZGC) |
| Shenandoah GC | OpenJDK 계열 저지연 GC (-XX:+UseShenandoahGC) |
📌 자주 사용하는 JVM 옵션
# 힙 메모리 크기 설정
-Xms2g # 초기 Heap 크기
-Xmx2g # 최대 Heap 크기
# GC 종류 선택
-XX:+UseG1GC # G1 GC 사용
# GC 로그 출력
-verbose:gc
-Xlog:gc*:gc.log # Java 9 이상
-XX:+PrintGCDetails # GC 상세 정보
-XX:+PrintGCDateStamps
# GC 튜닝 예시 (G1GC 기준)
-XX:MaxGCPauseMillis=200 # GC 지연 시간을 200ms 이하로
-XX:InitiatingHeapOccupancyPercent=45 # Old GC 시작 기준점 (%)
🧪 GC 튜닝 전략
1. GC 로그 분석
2. Heap 설정 최적화
- 너무 작은 Heap → 잦은 GC
- 너무 큰 Heap → GC 시간 증가, 메모리 낭비
- 예: 서비스 JVM 2~4GB Heap 정도에서 시작, 관측 후 조정
3. 객체 생존 시간에 따라 GC 전략 결정
- 단명 객체가 많다면 Young 영역을 키우는 게 유리
- 장기 객체가 많다면 Old 영역 GC가 핵심이 됨
4. Full GC 줄이기
- 메모리 누수 점검
- 캐시나 ThreadLocal 관리
- Heap Dump 분석하여 Old Gen에 무엇이 있는지 확인
5. G1 GC 튜닝 팁
-XX:+UseStringDeduplication # 중복 문자열 제거
-XX:MaxGCPauseMillis=200 # 최대 GC 지연 시간 설정
-XX:ParallelGCThreads=4 # GC에 사용할 스레드 수
-XX:ConcGCThreads=2 # Concurrent Mark 단계에 사용할 스레드 수
🔍 G1 GC 동작 구조 요약
- Heap을 여러 Region(영역)으로 쪼갬
- Young → Old → Humongous (대형 객체) 구조
- GC는 부분적으로, 병렬로, 동시적으로 실행됨
- 목표: 큰 Full GC를 피하면서 짧고 자주 GC
📈 튜닝할 때 유용한 도구
jstat -gc <pid>: JVM GC 상태 실시간 조회jvisualvm,Java Mission Control: GC 그래프와 Heap 확인VisualGC,HeapHero,GCEasy.io: 시각화