컨테이너 환경의 메모리 구조
기존 서버/VM 환경에서는 OS가 물리 메모리를 먼저 차지하고, 나머지를 JVM이 사용했습니다.
[VM/서버] 물리 메모리 → OS 커널 + 시스템 프로세스 → JVM
ECS 컨테이너는 다릅니다. 호스트 OS 커널을 공유하기 때문에, Task에 할당된 메모리는 거의 전부 컨테이너 프로세스(JVM)가 사용합니다.
[ECS Task] Task 메모리 ≈ JVM 전용
“OS 몫을 남겨야 한다"는 상식은 컨테이너 환경에서는 불필요합니다. Fargate든 EC2든 Task 내부의 메모리 설정은 동일합니다.
JVM 메모리 구조
JVM 메모리는 크게 Heap과 Non-Heap으로 나뉩니다.
| 구분 | 설명 | 설정 |
|---|---|---|
| Heap | 애플리케이션 객체가 저장되는 영역 | 개발자가 -Xmx로 설정 |
| Non-Heap | Metaspace, Thread Stack, Code Cache, GC, Direct Buffer 등 | JVM이 자동 할당 |
개발자가 직접 설정하는 건 Heap뿐입니다. Non-Heap은 JVM이 필요에 따라 알아서 할당합니다. 문제는 JVM이 컨테이너 메모리 한도를 신경 쓰지 않고 Non-Heap을 할당한다는 점입니다. Heap을 너무 크게 잡으면 Non-Heap이 남은 공간을 초과하고, ECS가 컨테이너를 OOM Kill(exit code 137) 할 수 있습니다.
Spring Boot의 Non-Heap 사용량
일반적인 Spring Boot 웹 애플리케이션의 Non-Heap 구성입니다.
| 영역 | 용도 | 일반적 사용량 |
|---|---|---|
| Metaspace | 클래스 메타데이터 | 200~500MB |
| Thread Stack | 스레드당 1MB 기본 (Tomcat 기본 200스레드) | 200~500MB |
| Code Cache | JIT 컴파일 결과 | 100~250MB |
| GC 오버헤드 | 가비지 컬렉션 | 수백MB |
| Direct Buffer | NIO 등 | 수십~수백MB |
Spring 공식 블로그의 Petclinic 실측 기준, Heap -Xmx48M 적용 시 Non-Heap 사용량은 약 223MB였습니다. 다만 이건 가벼운 앱 기준이고, Tomcat 기본 스레드(200개)만으로 200MB를 소비하므로 실무에서는 더 높습니다.
| 앱 규모 | Non-Heap 예상 |
|---|---|
| 간단한 앱 (스레드 적음) | 150~300MB |
| 일반적인 웹 서비스 | 300~600MB |
| 무거운 앱 (많은 라이브러리, 스레드 200+) | 600MB~1.5GB |
메모리 설정 가이드라인
“75% 룰"의 한계
흔히 “Heap은 Task 메모리의 75%“라고 알려져 있습니다. 4~8GB 컨테이너에서는 적절하지만, 메모리가 커질수록 이 비율은 맞지 않습니다. Non-Heap은 메모리 크기에 비례해서 늘어나지 않기 때문입니다.
본질은 비율이 아니라 Non-Heap 여유분(절대값)입니다.
권장 설정
| Task 메모리 | Heap | 여유 (Non-Heap) | 비율 |
|---|---|---|---|
| 2GB | 1~1.5GB | 0.5~1GB | 50~75% |
| 4GB | 3GB | 1GB | 75% |
| 8GB | 6~6.5GB | 1.5~2GB | ~80% |
| 16GB | 14.5GB | 1.5GB | ~90% |
| 32GB | 30GB | 2GB | ~94% |
메모리가 작을수록 비율이 낮아지고, 클수록 높아집니다. 75% 룰은 4~8GB 구간에서 절대값 기준과 대략 맞아떨어지기 때문에 흔히 쓰이는 것이고, 대용량에서는 그대로 적용하면 메모리 낭비입니다.
설정 방법
MaxRAMPercentage 사용
Java 10+ (또는 Java 8u191+)에서는 JVM이 컨테이너 메모리 제한을 자동 인식합니다.
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
간편하지만, 위에서 설명한 대로 메모리 크기별로 적정 비율이 다르므로 대용량에서는 비효율적입니다.
Xmx 직접 지정
-Xmx6g
Task 메모리에 맞춰 직접 계산해서 지정하는 방식입니다. 명확하고 예측 가능합니다.
Non-Heap 제한 (필요 시)
-XX:MaxMetaspaceSize=512m # Metaspace 상한 (기본값은 무제한)
-Xss512k # 스레드당 스택 사이즈 (기본 1MB)
-XX:MaxDirectMemorySize=256m # Direct Buffer 상한
실측으로 검증하기
권장 수치는 어디까지나 가이드라인입니다. 가장 확실한 방법은 실측입니다.
NativeMemoryTracking
JVM 옵션에 추가합니다.
-XX:NativeMemoryTracking=summary
실행 중인 프로세스에서 확인합니다.
jcmd <pid> VM.native_memory summary
실측한 Non-Heap 사용량에 20~30% 버퍼를 더하고, 나머지를 Heap으로 할당하면 됩니다.
CloudWatch Container Insights
ECS 서비스 단위로 메모리 사용량을 모니터링할 수 있습니다. MemoryUtilized와 MemoryReserved 지표를 확인하세요.
정리
- 컨테이너에서는 OS 메모리 고려가 불필요합니다 (Fargate, EC2 모두)
- Heap 외에 Non-Heap 영역(Metaspace, Thread Stack, GC 등)이 수백MB~1GB 이상 사용합니다
- “75% 룰"은 4~8GB 기준의 경험칙이고, 본질은 Non-Heap 절대값 확보입니다
- 실측(
NativeMemoryTracking) → 버퍼 추가 → 나머지를 Heap으로 할당하는 것이 가장 정확합니다