프로그램 개발(분석, 설계, 코딩, 배포)/2.2.1 java

자바 힙 메모리(Java Heap Memory) 분석 내용 총정리

3604 2025. 6. 30. 19:53
728x90

 

DEVOTEE 요약자바 힙 메모리 이슈 해결을 위한 자바 메모리 분석 툴을 사용하면서 가비지 컬렉터, 분석 툴과의 인터페이스 방법에 대해 이해하면서 관련 내용을 포스팅했다. 분석 툴을 이용하여 가비지 수집 과정, 가비지 수집기의 종류, JVM 상태 모니터링 등에 대해 알아보았다. 또한, JVM 상태를 대쉬보드로 표현하고, 메모리 설정값 변경에 따른 가비지 컬렉터의 그래프 변화, 메모리 누수 발생지를 찾는 방법을 실제 케이스로 알아보았다.

자바 힙 메모리(Java Heap Memory) 분석 내용 정리

자바 힙 메모리 이슈를 해결하기 위해, 자바 메모리 분석 툴을 찾아보기 시작했습니다.

분석 툴을 쓰려면 내용을 알아야 하기 때문에, 가비지 컬렉터, 분석 툴과의 인터페이스 방법에 대한 자료를 찾아봤습니다.

 

그리고 하나씩 알게 될 때마다 내용을 정리해서 하나씩 포스팅 했습니다.

한 눈에 보는 게 좋을 것 같아서 목차를 만든 내용을 공유합니다.

 

저도 필요한 부분부터 먼저 공부하고 자료 정리했기 때문에, 아래 글들의 실제 작성 순서는 번호 매긴 순서와 다릅니다.

필요한 부분 위주로 먼저 살펴보시면 됩니다.

Prolog. 분석 시작하게 된 계기

  • 가비지 컬렉션(Garbage Collection)이 제대로 안 되면 일어나는 일(Feat. 장애 경험담)
  • 참고 영상 : https://youtu.be/Y17EdphR3HA

 

01. Java Garbage Collector

Java 9부터는 기본 Garbage Collector로 G1(Garbage First)을 사용한다.

 

1. 가비지 수집이 일어나는 과정

1.1. 가비지 수집 개요

가비지 수집(GC, Garbage Collection)은 사용하지 않는 객체를 메모리에서 제거하는 작업이다.

다음은 가비지 수집 과정을 Visual GC로 캡쳐한 화면이다.

Eden Space가 급격히 떨어지는 구간이 Minor GC, Old Gen이 급격히 떨어지는 구간이 Major GC가 발생하는 부분이다.

1.2. 가비지 수집 과정

JVM에서는 '오래된' 객체를 제거하기 위해 메모리를 여러 영역으로 나눈다.

JVM 메모리 영역(Heap)내에서 객체의 이동

* 위의 이미지에서 보이는 Permanent 영역은 Java 8 이후로 삭제되었다. Java 8 이후로는 Metaspace에서 Meta 정보를 관리한다.

처음 생성된 객체는 Young Generation 영역의 일부인 Eden 영역에 위치하게된다.

그리고 Minor GC가 발생하게 되면, 사용하지 않는 다시말하면 다른 곳에서 참조되지 않는 객체는 메모리에서 제거된다.

Eden 영역에서 살아남은 객체는 Young Generation 영역의 또다른 일부인 Survivor 영역으로 이동하게된다.

Survivor 영역은 Survivor1 영역과 Survivor2 영역으로 구성되어 있다.

Minor GC가 발생할 때마다 Survivor1 영역에서 Survivor2 영역으로 또는

Survivor2 영역에서 Survivor1 영역으로 객체가 이동하게되며, 이 과정에서 더이상 참조되지 않는 객체는 메모리에서 제거된다.

Minor GC가 발생하는 동안 Survivor1, Survivor2 영역을 오가며 살아남은 객체들은 최종적으로 Old Generation 영역(=테뉴어드, Tenured)으로 옮겨진다.

Old Generation 영역에 있다가 미사용된다고 식별되는 객체들은 Full GC를 통해 메모리에서 제거된다.

Young Generation 영역에서 '오랫동안' 살아남은 객체는 Old Generation 영역(테뉴어드)으로 옮겨진다.

오래되었다고 하는 기준은 Young Generation 영역에서 Minor GC 가 발생하는 동안 얼마나 오래 살아남았는지로 판단한다.

각 객체는 Minor GC에서 살아남은 횟수를 기록하는 age bit 를 가지고 있으며, Minor GC가 발생할 때마다 age bit 값은 1씩 증가 하게 된다.

age bit 값이 MaxTenuringThreshold 라는 설정값을 초과하게 되는 경우 Old Generation 영역을 객체가 이동 되는 것이다.

또는 Age bit가 MaxTenuringThreshold 초과하기 전이라도 Survivor 영역의 메모리가 부족할 경우에는 미리 Old Generation 으로 객체가 옮겨질 수도 있다.

출처: https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html

테뉴어드 영역으로 승격되기 전까지 객체가 통과해야 할 가비지 수집 횟수는 다음 스위치로 조절할 수 있다.

-XX:MaxTenuringThrehold=<n>

Java 11에서 디폴트 값은 7이고 최대 15이다.

$ java -XX:+PrintFlagsFinal -version | grep 'TenuringThreshold' 
uintx InitialTenuringThreshold = 7 {product} {default} 
uintx MaxTenuringThreshold = 15 {product} {default}

MaxTenuringThrehold는 1~15의 한계치 값 조정 가능하다. 이 값을 바꿀 때는 다음 두 가지 상충되는 관심사를 잘 따져봐야 한다.

- 한계치가 높을 수록 진짜 장수한 객체를 더 많이 복사한다.

- 한계치가 너무 낮으면 단명 객체가 승격되어 테뉴어드에 메모리압을 가중시킨다.

한계치를 너무 낮게 잡으면 테뉴어드로 승격되는 객체가 증가하고 그만큼 더 빨리 공간을 차지하게 되어 풀 수집이 더 자주 발생한다.

(풀 수집 시에는 STW(Stop The World: 일시 정지)현상이 일어난다).

매사 그렇듯, 논 디폴트(non-default) 값으로 성능이 확실히 나아진 벤치마킹 사례가 없는 한 스위치를 함부로 변경하면 안 된다.

가비지 수집 과정을 동영상으로 보려면 다음을 참고한다(최대 힙 메모리를 1GB 설정했을 떄).

https://youtu.be/bNkjbVdk9Do

 

 

2. 가비지 수집기의 종류

2.1. Serial GC

단일 쓰레드 환경을 위한 가비지 수집기이다.

Serial Garbage Collector 사용을 위한 인수는 다음과 같다.

-XX:+UseSerialGC

VirtualBox VM에서는 기본 가비지 수집기를 Serial GC로 사용하고 있다(CPU Core 2개 사용해서 그렇게 되는 것으로 보인다).

$ grep -c processor /proc/cpuinfo
2
$ java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=16137664 -XX:MaxHeapSize=258202624 -XX:+PrintCommandLineFlags -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)

2.2. Parallel GC

멀티 CPU 환경에서 애플리케이션 처리 속도를 향상시키기 위해 사용되는 가비지 수집기이다.

Parallel Garbage Collector 사용을 위한 인수는 다음과 같다.

-XX:+UseParallelGC

Java 8 사용 시, 기본 GC를 ParallelGC로 사용하는 것을 확인할 수 있다.

$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=536870912 -XX:MaxHeapSize=8589934592 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (Zulu 8.38.0.13-CA-macosx) (build 1.8.0_212-b04)
OpenJDK 64-Bit Server VM (Zulu 8.38.0.13-CA-macosx) (build 25.212-b04, mixed mode)

2.3. Concurrent Mark Sweep (CMS) GC

CMS 수집기는 중단 시간을 아주 짧게 하려고 설계된, 테뉴어드(올드) 공간 전용 수집기이다.

대부분의 가비지 수집 작업을 응용 프로그램 스레드와 동시에 수행하여, 가비지 수집으로 인한 일시 중지를 최소화하려고 한다.

CMS Garbage Collector 사용을 위한 인수는 다음과 같다.

-XX:+UseConcMarkSweepGC

2.4. G1 GC

G1 (Garbage First) Garbage Collector는 대용량 메모리 공간 (4GB 이상)이 있는 멀티 프로세서 시스템에서 실행되는 응용 프로그램을 위해 설계되었다.

- 중단 시간이 짧은 새로운 수집기로 설계되었으며, CMS보다 훨씬 튜닝하기 쉽다.

- 조기 승격에 덜 취약한 장점이 있다.

. 조기 승격(premature promotion): 할당률이 너무 높아서 객체가 테뉴어드로 빨리 승격되는 문제

. 할당률: 일정 기간(단위는 보통 MB/s) 새로 생성된 객체가 사용한 메모리양

자바 9부터는 G1을 디폴트 수집기로 사용한다.

G1 Garbage Collector 사용을 위한 인수는 다음과 같다.

-XX:+UseG1GC

Local 맥북(12Core), 개발기(4Core), 운영기(4Core)에서는 모두 G1GC를 사용하고 있다(별도 설정하지 않았다).

#Local(맥북)
$ sysctl hw.ncpu 
hw.ncpu: 12

$ java -XX:+PrintCommandLineFlags -version -XX:+UseG1GC
openjdk version "12.0.2" 2019-07-16
OpenJDK Runtime Environment (build 12.0.2+10)
OpenJDK 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)

#개발기
$ grep -c processor /proc/cpuinfo
4

$ java -XX:+PrintCommandLineFlags -version -XX:+UseG1GC
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)

#운영기
$ grep -c processor /proc/cpuinfo
4
$ java -XX:+PrintCommandLineFlags -version ... -XX:+UseG1GC
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)

다음은 디폴트 중단 목표를 200밀리초로 설정하는 스위치이다.

-XX:MaxGCPauseMillis=200

실제로 중단 시간을 100밀리초 이하로 설정하면 현실성이 너무 떨어져 수집기가 지키지 못할 공산이 크다.

로컬/개발기/운영기에서는 디폴트 값인 200으로 되어 있었다.

$ java -XX:+PrintFlagsFinal -version | grep 'MaxGCPauseMillis'
uintx MaxGCPauseMillis = 200 {product} {default}

openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)

* 주의사항: Java에는 수백 가지의 스위치 항목이 있지만, 확실한 성능 개선 효과 레포트가 있지 않다면 상세 옵션은 손대지 않는 것이 좋다.

G1 GC에 대한 자세한 설명은 다음을 참고한다.

- https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

- https://docs.oracle.com/en/java/javase/11/gctuning/garbage-first-garbage-collector.html

- https://www.oracle.com/technical-resources/articles/java/g1gc.html

 

2.5. 그 외 GC

ZGC, Shenandoah, Epsilon GC가 있다.

ZGC와 Shenandoah는 모든 어플리케이션 쓰레드를 정지하지 않고도 힙 안의 오브젝트를 이동시킨다.

Epsilon GC는 '아무것도 안 하는' 수집기이다. 힙에서 객체를 반환하지 않기 때문에, 힙이 다 차면 Out-of-memory error가 발생한다.

이는 제한된 메모리를 사용하는 임베디드 환경에서 유용할 수 있다.

위 세 개의 GC는 글 쓰는 시점(2020년 8월) 기준으로 실험 단계에 있는 GC이다.

$ java -XX:+UnlockExperimentalVMOptions -XX:+PrintFlagsFinal -version
... bool UseEpsilonGC = false {experimental} {default}
... bool UseShenandoahGC = false {experimental} {default}
... bool UseZGC = false {experimental} {default} ...

→ 실험 단계가 끝나면 '{experimental}'이 '{product}'로 바뀐다.

실험 단계에 있기 때문에, 각 GC 알고리즘을 사용하기 위해서는 '-XX:+UnlockExperimentalVMOptions'를 같이 써야 한다.

 

예)

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC [프로그램명]

3. 다양한 GC 알고리즘의 지원 수준

JDK 버전에 따라 채택한 GC 알고리즘은 다음과 같다.

GC 알고리즘JDK 8 지원JDK 11 지원JDK 12 지원
Serial GC S S S
Throughput(Parallel) GC S S S
G1 GC S S S
Concurrent Mark-Sweep(CMS) S D D
ZGC - E E
Shenandoah E2 E2 E2
Epsilon GC - E E

(S: Fully Supported D: Deprecated E: Experimental E2: Experimental; in OpenJDK builds but not Oracle builds)

​4. 기타 특이사항

강제로 Full GC를 발생시키려면 다음과 같이 한다.

$ jps 
--> proc_id 확인

$ jcmd [proc_id] GC.run

Java 스위치(JVM 플래그) 확인하기 - 이전에 정리했던 자료​

https://blog.naver.com/pcmola/222050542369

5. 참고자료

- 자바 최적화(한빛미디어)

- Java Performance 2nd edition(O'REILLY)

- https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html

- https://dzone.com/articles/evolution-of-the-java-memory-architecture-java-17

- https://docs.deistercloud.com/content/Axional%20development%20libraries.20/Axional%20Server.4/Tunning.xml?embedded=true

- https://www.perfmatrix.com/type-of-garbage-collector/

 

출처 : https://blog.naver.com/pcmola/222060198638

02. Java Heap Memory 분석 방법

어떤 데서 메모리를 많이 쓰고 있는지 분석하기 위해 좀 뒤적뒤적하면서 찾아보고 실습해 본 내용을 정리했다.

주로 '자바 성능 튜닝' 책(비제이 퍼블릭 출판)을 참고하였다.

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222038466393

03. JMX(Java Management eXtension)

* 실행 중인 Java의 기능을 모니터링하기 위해서는 원격 서버에서 JMX 연결이 허용되어 있어야 한다.

* 보안 상 위험에 노출될 수 있으므로, 운영기에서는 특수한 상황이 아닌 한 JMX 옵션을 켜두지 않는다.

 

JMX(Java Management eXtensions)는 응용 프로그램(소프트웨어)/객체/장치 (프린터 등) 및 서비스 지향 네트워크 등을 감시 관리를 위한 도구를 제공하는 자바 API이다.

응용 프로그램(소프트웨어)/객체/장치, 서비스 지향 네트워크는 MBean(Managed Bean)이라는 객체로 표현된다. (출처: 위키피디아)

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222061574743

 

04. JVM 상태 모니터링 jstat, jstatd

JVM 통계 모니터링에 대해 소개한다.

콘솔로 접속해서 모니터링할 수 있는 jstat,

데몬 형태로 띄워서 RMI 통한 원격 모니터링을 할 수 있는 jstatd에 대해 설명한다.

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222062377357

 

05. GC Logging 및 분석 도구(GCeasy)

JDK 11 이상 기준으로 Java Gabage Collection Logging하는 방법과 분석 도구에 대해 설명한다.

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222072076240

 

06. Java 프로파일링 도구

Java 프로파일링 시 용도에 따라 다음 도구를 사용한다(모두 무료이다).

힙 덤프 메모리 분석: MAT 사용

- 덤프 파일이 GB 단위까지 되는 경우가 있다.

일반적인 인터넷 망에서는 중간에 다운로드가 끊길 수 있으므로 파일 다운로드 및 분석용의 별도 VM이 필요하다.

CPU/Memory 실시간 모니터링: Visual VM 사용

- JMX 포트가 열려있어야 한다.

Garbage Collector의 실시간 모니터링: Visual VM + Visual GC 사용

- jstatd 포트가 열려있어야 한다.

시스템 전반적인 상태 분석: JDK Mission Control + JFR 사용

- JMX 포트가 열려있어야 한다.

GC Log 분석: GCeasy

- 웹으로 분석하는 툴이기 때문에, 인터넷에 연결되어 있어야 한다.

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222064546600

 

07. Case Study(Memory Leak)

Memory Leak을 발생시키는 예제를 만들어 실행시킨 다음에,

자바 힙 메모리 분석 도구(jhat, mat)를 통해 메모리 누수 발생지를 찾는 방법을 소개한다

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222064258040

 

08. Case Study(메모리 설정값 변경)

Hyperledger Besu 실행 시 시작메모리(-Xms), 최대메모리(-Xmx) 설정값을 변경함에 따라

Garbage Collector의 그래프 모양이 변하는 것을 확인할 수 있다.

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222064880474

 

09. JVM 상태를 대쉬보드로 표현하기

Java > Jolokia > Telegraf > InfluxDB > Grafana로 모니터링하기

 

상세 내용은 아래 블로그에 정리하였으니 참고 부탁드립니다.

https://blog.naver.com/pcmola/222099371293

 

출처: https://devocean.sk.com/blog/techBoardDetail.do?ID=165630

728x90