개발/BE

[Java] Heap Memory 누수 분석기2 : 모니터링 환경 구축하기 - VisualVM

kkap999 2024. 2. 28. 02:26
728x90

이전 글에서 이어집니다 : [Java] Heap Memory 누수 분석기1 : JVM이란 ?

그래서 JVM이 뭔지 대략적으로 알았으니 내 프로그램에서 이 JVM이라는 녀석이 어떻게 돌아가고 있는지 확인해보려고 한다.
일단 여러가지 모니터링 툴이 있다고 해서 써봤는데, 분석이 쉽지 않았다.

(첨에는 InteliJ에 내장되어있는 Profiler를 통해서 보려고 했으나 서비스 프로세스에 부하 좀만 주면 프로세스 다운 전에 InteliJ 자체의 메모리가 터져서 죽어버렸다 ;;;)

가장 간편한 방법은 바로 힙 덤프를 떠서 확인해보는거였지만, 고객들에게 제공되고있는 서비스였기때문에 막 덤프 떠버리기는 좀 부담이 있었다.
특히 구글링 중 봤던 아래 문구때문에 힙덤프는 마지막 후순위로 미루고싶었음

주의!!
힙 히스토그램을 생성하려면 어플리케이션에 부하가 발생한다.
어플리케이션에서 통계정보를 받아야하기 때문이다.
실제로 동작하는 프로덕션에서는 절대로 사용하지 말 것

아 근데 이거 진짜니까 조심해야함
특히 Memory Leak이 쌓인 상태에서 -dump:live 옵션까지 주면 CPU랑 MEM 터질 수 있음 주의
이 옵션 없어도 리소스 사용량이 눈에 띄게 증가했었다.

하지만 결론부터 말하자면 힙덤프나 서버 재실행 없이 원인 분석하기는,,, 당시의(지금도) 내가 느끼기엔 그냥 불가능이었다. 
시도했던 방법들 중 하나가 VisualVM을 활용하는거였는데, 이에 대해서 소개하려고 한다.

VisualVM

실행중인 프로세스의 IP:port 기반으로 수집한 정보들을 분석하여 시각적으로 보여주는 툴이다.
VisualGC를 통해 JVM의 영역들에 대해 확인할 수 있고, 각각의 영역이 얼마나 사용중인지도 손쉽게 확인할 수 있었다.
JVM 구조에 대해서 이론적으로 공부하고, 실제로 어떻게 돌아가는지 직접 확인해봄으로서 JVM에 대해 이해하는데 아주 도움이 많이 됐었다.

Monitor 탭에서 GC를 직접 수행하거나, 실시간으로 힙덤프를 떠서 확인하는 등 UI를 통해 편리한 기능들을 제공했다.

하지만 상용환경에는 방화벽땜에 IP:port를 외부에서 접근하기도, 그렇다고 서버 내부에 툴 설치해서 UI 띄우기도 좀 어려운 상황이었다.

그래서 그냥 로컬 환경에서 API 호출 시나리오 별로 테스트할 때, 실시간 모니터링하는 목적으로 사용했었다.

남은 방법은 jhat, MAT, prometheus 등을 확인하는 방법이 있는데,,
jhat, MAT는 덤프파일이 필요하고 prometheus metric을 수집하려면 서버 재기동이 필요해서
그냥 dump 생성 작업을 잡고 덤프떠서 덤프파일을 통해 분석했었다.

아무튼 그렇지만! 로컬 환경(리모트 없음)에서 VisualVM사용했던 방법에 대해서 정리해보고자 한다.(MacOS)
주로 사용했던 기능 몇 개만 정리해보았다.

1. 설치

설치는 공식 링크에서 하면 된다.
https://visualvm.github.io/

 

VisualVM: Home

News: September 19, 2023: VisualVM 2.1.7 Released This release adds support for JDK 21 and delivers a bunch of bugfixes. See the Release Notes for all changes. The tool can be downloaded from the Download page, sources are available in release217 branch. A

visualvm.github.io

여기서 도큐먼트 등 다양한 정보를 확인할 수 있다.

다운로드 후 실행하면 왼쪽에 VisualVM 통해 모니터링 가능한 프로세스들을 확인할 수 있다.


2. VisualGC 플러그인 적용

위에 캡쳐한 JVM 영역들에 대한 GUI 모니터링을 하려면 VisualGC 플러그인이 필요하다.
해당 플러그인을 적용해보자

VisualVM 상단 탭 - Tools - Plugins
에 들어가면 아래와 같은 창이 뜨는데, 여기서 VisualGC를 찾아서 Install해주면 된다.
(이미 설치해놔서 Installed에 있음)

적용 후에 Applications 탭에서 보고싶은 프로세스를 선택하고, VisualGC 탭으로 들어가면 

wow~ 지난 포스팅에서 공부했던 Heap Space의 구조와 GC가 일어나는걸 실시간으로 확인할 수 있게 된다!

이건 예시 프로그램이라 이상적인 모양새는 아니지만, 직접 확인해보면

  • Survivor 영역은 0/1 둘 중에 하나만 사용되며 Eden Space가 초기화되는 MinorGC가 일어날 때 교체된다.
  • Eden Space는 Minor GC가 일어날때마다 비워진다.
  • 메모리 누수가 일어나는 경우, MajorGC가 발생해도 남아있는 객체들에 의해 Old Gen 사용량이 계속 올라간다.

글 쓰던 와중에 의문점이 몇 개 생겼는데,,

  • Minor GC가 일어나는 에덴 영역의 기준이 뭐지 ?
  • 기본 Xmx와 Xms 크기는 얼마나 되고 어떻게 정해지는걸까 ?
  • Stop-the-world 상태에서 시스템 영향도 측정할 수 있는 방법이 따로 있을까?

JVM과 gc에 대해서 이렇게나 잘 모르는데 어떻게 memory leak 원인은 어떻게 찾았었는지 싶다.
얼른 공부해봐야 할듯

3. Heap Dump 생성하기

Monitor 탭에서 실시간으로 GC나 덤프 생성 작업을 수행할 수 있다.
Perform GC : 실시간 Full GC
Heap DUmp : 실시간 heap dump 파일 생성

4. Heap Dump 분석하기

Monitor - Heap Dump를 통해 덤프를 생성하거나 [ File - Load ] 를 통해서 덤프파일을 불러와서 분석할 수 있다.

덤프파일을 열어서 어느 객체가 메모리를 많이 잡아먹고있는지 보려면 Objects 탭을 확인한다.
누수가 일어나는 부분을 중점적으로 확인하려면 Retained Memory가 발생하는지 보면 된다.
Size나 Retained 기준으로 정렬 가능하므로 정렬해서 확인해보자

특정 객체를 열어보면 각 인스턴스별로 할당된 메모리의 크기나 주소값, 패키지 경로 등을 확인할 수 있다.
패키기 경로나 메서드 등의 정보를 통해서 역추적하면 누수 발생 지점을 찾을 수 있을 것이다.

 

대충 사용했던 기능은 이정도인 것 같다.
로컬에서 API 계속 쏘면서 Old Gen 영역과 Full GC 횟수 확인하고
메모리가 좀 누적된 것 같으면 Heap Dump 떠서 분석하고,,

하지만 사실 로컬 환경에서는 재현이 안됐다.
로컬에서 단기간에 요청할 수 있는 API 호출횟수나,, DB서버와 같은 상면에 위치해있다거나 등의 다른 제약조건들이 많아서 그랬던 것 같다. (API 요청에는 nGrinder를 사용했었는데 이 사용법에 대해서는 JVM 관련 포스팅이 끝난 후 정리할 예정이다.)
그래서 상용 환경에서 덤프파일을 떴고, jhat과 MAT, VisualVM 등의 툴을 통해 추가적으로 분석하였다.

다음 포스팅에서는 덤프 생성 명령어인 jmap과 jhat 명령어에 대해서 정리할 예정이다.

 

오늘의 교훈
- 자바로 개발할거면 자바랑 JVM 공부 열심히 하자
- 자원 효율화에 대해 공부를 해봐야겠다.