개요
오늘은 개발자가 JVM 메모리 중에서도 Heap 메모리에 대해서 알아보려고 합니다.
Heap - JVM 튜닝 포인트, 잠재적 OOM문제 발생 지점

Oracle Java Virtual Machine Specification에서 찾은 튜닝포인트
Chater 2. The Structure of teh Java Virtual Machine에서 내용 발췌
“현 JVM 규격에 명시되어 있지 않은 구현과 관련된 상세 사항들은 구현자의 창의성을 저해하는 불필요한 제약이 될 수 있습니다. 예를 들자면, 데이터 영역의 메모리 레이아웃이나 Garbage-Collection에 사용되는 알고리즘, 그리고 JVM 명령어 실행과 관련된 내부의 최적화(e.g, 기계어로 번역하는 과정)에 관한 것은 구현자의 재량으로 남겨두겠습니다.”
💡 다시말하면 JVM의 튜닝포인트가 바로 위에 명시된 부분들이 될 수 있다는 말이 됩니다.
튜닝포인트 3가지
- 데이터 영역에서의 메모리 레이아웃 튜닝
- 여러 옵션 (-Xmx, -Xms, -XX:MaxMetaspaceSize …)
- 적절한 GC 알고리즘 선택
- 명령어 실행과 내부 최적화
- JIT 컴파일러단
Runtime Data Area의 메모리 레이아웃
힙 메모리(Heap Memory)
힙 메모리는 Java 프로그램에서 동적으로 생성되는 객체들은 모두 힙 메모리에 할당됩니다. 힙 메모리는 Java 애플리케이션의 메모리 사용량과 객체의 생명주기에 따라 동적으로 확장 및 축소됩니다.
→ 관리를 제대로 하지 못하면, OOME가 발생할 수 도 있습니다.
힙 메모리에 대해서 좀 더 자세히 알아봅시다.
On-heap과 Off-heap

On-heap Store(그림에서 JVM Heap)
Java에서 생성된 객체가 저장되는 힙 영역을 좀 더 자세하게 On-heap store라고 합니다.
On-heap store에 객체를 저장하다 보면 GC 오버헤드(특히 Full GC) 때문에 애플리케이션의 성능이 저하되는 경험을 하게 됩니다.
- 굉장히 큰 사이즈의 데이터를 생성할때
- 굉장히 많은 숫자의 객체를 생성할때
GC 대상 객체들의 증가로 애플리케이션이 버벅일 가능성이 높아집니다.
Off-heap Store (= Native Memory)
Off-heap store는 '힙 밖에 저장한다' == ‘GC 대상으로 삼지 않겠다’ 라는 의미를 가지고 있습니다.
Off-heap Store에 저장되는 객체는 직렬화한 다음 저장해야 합니다.
Direct Memory
Native memory에 포함되어 있는 memory 이지만, OS에 제공하는 pagecache 기능을 direct하게 사용할 수 있는 공간입니다.
첫번째 튜닝 포인트
💡 Off-heap의 사용이 GC 오버헤드의 해결방안이 될 수 있다.
Off-heap을 사용하는 방법 2가지
- Direct Memory사용
- Java에서는 nio를 통해 Off-heap store의 사용을 제공하고 있습니다. Direct ByteBuffer를 이용해 할당받은 버퍼 공간은 GC의 대상이 되지 않는 Off-heap store에 저장됩니다.
- 외부 라이브러리 사용
- GC가 대신해주는 메모리 관리등을 직접 어플리케이션에서 해줘야 하기 때문에 EhCache 같은 외부 라이브러리를 사용하는 방법도 있습니다.
Trade-Off
Off-heap 메모리를 사용하면 GC에 의한 일시적인 중지나 오버헤드를 피할 수 있으며, 특히 대규모 데이터 처리나 실시간 처리(Kafka - Direct Memory)를 위한 빅데이터 플랫폼에서 유용할 수 있습니다.
하지만, heap과 반대로 덤프나 jstat 같은 java 일반적인 메모리 분석 방법이 안되어, jcmd와 같은 추가적인 도구를 사용해서 확인해야 합니다.
이와 관련된 이슈 및 트러블 슈팅 사례
Kafka OOM관련

Native Memory Leak이 발생하여 삽질을 하신 토스의 개발자분

도커의 팟 MAX 메모리 : 4GB
Java의 Max Heap Size : 1.5GB

왼쪽의 위치한 Java Heap의 Max Size(-Xmx옵션)가 1.5GB
오른쪽의 위치한 Metaspace 공간 + 기타 메모리 공간 ≥ 2.5GB가 된 상황
실제 사용량 측정

java파일을 실행시 아래 옵션 활용
-XX:NativeMemoryTracking=summary
이후, jcmd명령어를 사용하여 사용 메모리 확인
jcmd <pid> VM.native_memory summary
top Linux 명령어 -> RES 수치 확인
💡 왼쪽과 오른쪽간 2GB의 메모리 차이 ⇒ 높은 확률로 Native Memory Leak
예상 범위 찾기

Direct Buffer로 할당된 메모리는 -XX:NativeMemoryTracking
-XX:NativeMemoryTracking
옵션의 결과에서 확인할 수 있다.
(GC의 대상이 되지 않을 뿐 JVM에서 모니터링은 하고 있다)
- Direct Buffer를 JVM에서 모니터링한다?Native 메모리를 참조하는 객체는 결국 JVM Heap 안에 생성되며, 이 객체가 JVM의 GC에 의해 회수되면 이 객체가 참조하는 Native 메모리는 JVM이 아닌 다른 메커니즘에 의해 어쨌든 회수된다.
- 참조 https://homoefficio.github.io/2020/08/10/Java-NIO-FileChannel-과-DirectByteBuffer/
위 3가지 범위에서도 찾지 못하여 Linux Process 레벨까지 내려간다.
Reference
Inside the Java 2 Virtual Machine: Venners, Bill
https://soft.plusblog.co.kr/163
https://www.youtube.com/watch?v=GU254H0N93Y&t=85s
'개발' 카테고리의 다른 글
| Redis의 자료구조 (List, Sorted Set) (0) | 2025.01.19 |
|---|---|
| SOLID Principle 알아보기 - 두번째 (0) | 2025.01.05 |
| SOLID Principle 알아보기 - 첫번째 (0) | 2024.11.24 |
| JVM 첫번째 글 - JVM의 구조와 동작에 대하여 (0) | 2024.10.27 |
| 다양한 페이징 기법(pagination)과 장단점 (1) | 2024.10.13 |