자바 프로세스에서 메모리릭을 의심할 수 있는 정황이 보일때 Heapdump를 생성하고 분석도구를 이용해 메모리릭의 원인을 찾아볼 수 있습니다. 관련해서 찾아본 내용을 공유드립니다.

 

 

 

자바 프로세스 Heapdump 생성하는 방법과 이클립스 MAT을 이용해 Heapdump 분석하는 방법

 

 

 

 자바 메모리 누수 (Memory Leak)와 OOM의 상관관계에 대해 

 

메모리 누수와 OOM는 직접적인 상관관계는 없지만, OOM이 발생했다면 메모리 누수를 의심해볼 수 있다.

 

 메모리 누수는 프로그램에서 사용하지 않는 객체를 메모리에서 해제하지 않아서 계속 쌓이거나, 해제를 하는 것 보다 더 빠르게 특정 객체가 메모리를 점유하게 되면 JVM이 새로운 객체를 위한 메모리를 할당하지 못하면서 OOM이 발생하게 된다.

 

 

 

 톰캣에 배포된 프로그램이 OOM이 일어나진 않았지만 메모리 사용량이 JVM xmx 설정보다 높을 때 

 관련 조사를 하게된 이유는 jvm 옵션 중 Xmx로 최대 Heap 크기를 지정했음에도 대상 자바 프로세스가 메모리 사용량이 그보다 훨씬 높은 현상을 확인했기 때문이다. 물론 메모리 사용량만으로 메모리릭이라고 확정지을 순 없지만, 지정한 heap 크기를 훨씬 상회하는 메모리 사용량을 보였기 때문에 의심이 된다는 내용의 이슈를 전달받아 조사하게 됐다.

 

 아래는 톰캣에 올린 웹서버를 ps 명령어를 통해 나열된 프로세스 정보의 일부이다. 자바 프로세스를 아래 명령어로 확인하면 적용된 jvm option도 함께 확인할 수 있다.

> ps -ef | grep java

/usr/bin/java 
-Dfile.encoding=UTF-8 
-server 
-Xms1536m 
-Xmx1536m 
-XX:NewSize=512m 
-XX:MaxNewSize=512m 
-XX:MetaspaceSize=512m 
-XX:MaxMetaspaceSize=1024m

 

- Xms : 초기 Heap 크기 지정

- Xmx : 최대 Heap 크기 지정

위 두 옵션은 자바 어플리케이션의 메모리 사용량을 제한하는데 사용된다.

 

 

 ps 명령을 통해 얻은 대상 프로세스의 pid를 이용해서 메모리 사용량, cpu 점유율등을 알 수 가 있고 적용된 Xmx 값까지 확인할 수 있었다. 근데 문제는 터미널 명령어를 통해 확인되는 모든 메모리는 실제 사용량과 차이가 있으며, 단일 프로세스의 실제 정확한 메모리 사용량은 알기 어렵다고 한다. 따라서 정확한 값은 아니라는 사실만 참고로 알고 있으면 된다.

 

  어찌 됐든 표시되는 메모리 사용량이 Xmx에 표시된 값보다 크기가 컸고 원인은 다양할 수 있다. (1) java7부터 메모리 구조가 변경되어 native 메모리 사용량이 포함되어 그럴 수 도 있고, (2)설정한 메모리 크기보다 실제 사용량이 훨씬 크기 때문이거나 (3) 실제로 메모리 릭이 있을 수 도 있다. 

 

 나의 경우에는 프로세스에서 사용중인 메모리가 Xmx에 지정한 메모리보다 2배가까이 컸기 때문에 2번, 3번을 가정하고 메모리 릭에 대해 검토하면서, 해당 프로그램이 현재 구조에서 사용될 때 일반적인 메모리사용량이 얼마나 되는지를 확인해보기로 했다.

 

 

 

 자바 프로세스 Heap Dump 뜨는 방법 

 

1. 명령어를 통해 heap dump 생성하기

//pid가 1234인 자바 프로세스의 heapdump 파일을 대상 디렉토리에 생성
jmap -dump:format=b,file=/tmp/heapdump.hprof 1234

 jmap 명령어를 사용하면 JVM 내부에서 사용하는 메모리 정보를 출력하거나, 위 옵션으로 명령을 수행하면 Heap Dump를 특정 디렉토리에 내려줄 수 있다. heap dump 파일은 hprof 확장자를 가진다.

 

 

2. OOM 예외 발생시 heap dump 자동 생성하기

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump/directory

JVM 옵션에 위 옵션을 추가해 OOM 발생시 지정한 디렉토리에 heapdump를 저장하도록 설정할 수 있다. 관련해서 자세한 순서는 추후 알아볼 예정.

 

 

 

 리눅스 장비에서 생성한 heapdump 파일을 윈도우로 옮기는 방법 

 heapdump 파일 옮기는 방법을 소개하는 이유는 운영에서 사용하는 리눅스 서버장비로 기타 장비에서 터미널을 통해 접근하기 때문이다. 리눅스 서버장비 자체가 가상화 장비인 경우도 있고 운영주체가 다를 수도 있기 때문에 리눅스장비 ~ 내 개발장비로 heapdump 파일을 이동하는 방법에 대한 것도 알아두어야한다.

 

1. scp 이용하기

scp user@linux-server:/path/to/dump/directory/heapdump.hprof C:\path\to\windows\directory

명령어에 알맞은 파라미터를 입력해준다.

- linux-server : 리눅스 서버 ip

- /path/to/dump/directory/[파일명] : 리눅스 서버에 heapdump가 저장된 디렉토리명

- C:\directory : 윈도우에 저장할 디렉토리명.

 

scp user@123.123.123.123:/home/log/heapdump.hprof .

요렇게 입력하면 cmd 터미널의 현재 디렉토리에 해당 파일을 가져오게 된다. 원하는 디렉토리로 이동하고 명령어를 수행하면 좀 더 편하게 사용할 수 있다.

 

 scp는 서로다른 서버간에 파일을 복사해오는 개념이기 때문에 다량의 파일을 복사 붙여넣기하는 작업이 힘들어질 수 있다.

 

 

2. SFTP 이용하기

sftp로도 사용할 수 있다. 직접 사용해보진 않았고 추후에 작성 예정. scp와 동일하게 ssh 프로토콜으로 파일을 전송하는 명령어인데, scp는 단순 복사에 적합한 것에 비해 sftp는 cui 인터페이스를 제공해서 좀 더 쉽고 간편하게 여러 파일을 전송할 수 있다고 한다.

 

 

 

 

 메모리 릭 분석 툴, Eclipse MAT 설치 및 실행하기 


자바 프로세스의 Heap Dump 파일을 생성하고 개발장비로 파일을 잘 옮겼다면 아래와 같은 분석툴을 이용해 heap dump를 분석할 수 있다.

 

https://www.eclipse.org/mat/

 

다운로드 페이지에 들어가면 운영체제별로 MAT 프로그램 설치파일을 제공하고 있다.

 

https://www.eclipse.org/mat/downloads.php

Mac 설치파일은 두가지 제공하고 있는데 x86_64는 인텔 cpu, AArch64는 애플 실리콘을 말한다. 각자 사용중인 운영체제에 맞게 설치하면된다.

 

일단 설치하고나면 여러가지 문제가 생겨서 실행이 안될 수 가 있는데, 처음으로 고려해야할 부분은 최신 버전의 MAT은 jdk 17 이상이 환경변수에 등록되어 있어야 한다.

 

저는 맥에 설치 했는데 실행하니 아무것도 실행되지 않고 무반응인 문제였는데 관련해서 검색해보아서 나온건 실행 파일을 Appplication 디렉토리로 옮겨라는 거여서 옮겨도 실행이 안됐다. 그리고 당연히 jdk는 설치되어있지만 버전을 올리기가 어려운 상황이라 윈도우 장비에서 설치를 완료했다.

 

 

MAT은 이클립스에서 만든 오픈소스 프리웨어로 자세한 라이선스 정보는 위 설치디렉토리에서 notice.html을 실행하면 확인할 수 있다. MemoryAnalzer 실행파일을 통해 실행할 수 있다.

 

실행되면 File > Open Heap Dump .. 항목으로 hprof 확장자의 heapdump 파일을 열 수 있다.

 

 

 

 

 

 MAT을 이용해 HeapDump 분석해보기 

 

MAT은 메모리릭이 의심되는 객체에 대한 분석을 해주는 도구이다. 실무에서 의심되는 정황에 대한 내용전달을 받았고 그래서 분석을 진행하게 됐는데, 결론부터 말하면 문제가 발생한 배포 환경과 여러가지가 상황이 달라 의심점을 찾지는 못했다. 하지만 실제 분석하는 과정은 동일하기 때문에 간단하게 분석하는 과정 경험한 내용을 공유하려한다.

 

파일을 열면 이렇게 바로 해당 heapdump의 메모리 릭에대한 리포트를 확인할 것인지 여부를 묻는다. 체크하고 Finish 버튼을 클릭하면 된다. 클릭하지 못해도 이후 버튼에서 확인할 수 있다.

 

 

요렇게 heap dump 파일을 읽어 메모리 릭으로 의심되는 객체들에 대해서 언급하고 리포팅을 진행해준다. 

 

 원형 그래프를 클릭해보면 좌측 상단에 자세한 조사창이 나오는데, 해당 객체의 클래스와 메모리 주소등등 정보들이 나온다. 제일 하단에는 no GC root가 적혀있는데 해당 객체가 GC root로부터 도달할 수 없는 객체라는 의미로 대상 객체를 어떤 객체도 참조하지 않는 경우를 의미한다. 따라서 No GC root는 MAT에서 메모리 누수를 의심할 수 있는 객체들을 수집하는데 중요한 지표 중 하나인 것으로 보인다.

 

 하지만 heap dump를 생성하는 시점에 no GC root인 객체라고 하더라도 런타임에서 찰라의 heap 영역의 스냅샷이기 때문에 저 객체들이 반드시 메모리릭의 원인이라고 단정지을 수 없다. 다만 용의선상에 올리고 의심해볼만한 항목들이라는 의미정도를 가지기 때문에 일단 의심을 시작해본다.

 

 

 이 heapdump 같은 경우에는  MAT이  단일 인스턴스가 가장 큰 크기를 가진 인스턴스의 클래스를 용의선상에 올렸는데 두 클래스는 톰캣에서 사용하는 클래스로더와 정적 리소스 캐시를 위한 클래스였다. 이 경우에는 JVM에서 관리하는 인스턴스이고 위 두 클래스가 로딩하고 캐시하는 객체가 메모리릭의 원인이 될 순 있지만 대상 객체가 원인이 되기는 어려워 보였다.

 

 MAT에서는 heapdump를 뜨는 시점에 의심할만한 정황이 있는 객체들을 보여줬을 뿐이기 때문에 리포트를 보고 정확하기 판단해보는 것이 중요하다. 따라서 위 경우에서는 의심할만한 근거가 부족하기 때문에 다른 부분에서 원인을 찾아보아야 한다.

 

 참고로 OOM이 발생한 시점 자동생성된 HeapDump를 MAT이 분석한 Memory Leak 리포트에서는 stack trace를 확인할 수 있는 링크가 포함된다. 그냥 heapdump를 생성하는 것보다는 jvm 옵션을 추가해서 Exception 발생한 heapdump를 분석하는 것이 훨씬 원인 분석에 도움이 될 것으로 보이기 때문에 heapdump를 생성하는 시점도 중요한 것으로 생각된다. 하지만 OOM이 발생하지 않아도 메모리릭은 존재할 수 있기 때문에 적절한 주기로 분석해 미리미리 원인을 제거하는 것이 중요할 것 같다.

 

 

 

켜자말자 실행된건 오른쪽 Leak Suspects이고 다양한 방법으로 heapdump를 분석해볼 수 있지만 Histogram으로도 분석해볼 수 있다. 

 

 

크기 내림차순으로 정렬되어 있는데 Shallow Heap은 대상 클래스 메모리 그 자체를 의미하고 단위는 byte이다. 

 

 

List objects를 통해 대상 클래스로 분류된 인스턴스들을 확인해볼 수 있다. 

 

아래 두 항목은 메모리 덤프 파일에서 객체에 대한 참조 관계를 나타낸다.

with outgoing references : 대상 객체가 참조하는 다른 객체들을 보여줌

with incoming references : 대상 객체를 참조하는 다른 객체들을 보여줌

 

보통은 특정 객체를 원인으로 추정하고 분석하는 것이기 때문에, incoming references를 통해 해당 객체를 참조하는 객체들을 분석하고 실제 코드상에서 객체를 생성한곳 사용한 곳등을 변경해서 메모리 누수를 방지할 수 있다.

 

요렇게 참조하는 객체를 따라가며 의심되는 객체를 추려볼 수 도 있고 동일한 크기, 값을 가지면서 여러개가 있다던지 그런 케이스를을 통해 의심가는 객체를 찾아나가야한다. 그 외에도 MAT에는 두가지 heapdump를 비교하여 분석할 수 도 있는등 여러가지 기능들을 제공한다. 

 

이상으로 heapdump 생성 및 이클립스 MAT 도구를 통한 heapdump 분석하는 과정을 전반적으로 알아보았다. 아직 분석이 진행중이라 여기 포스팅에서도 메모리릭을 의심하면서 그 과정을 밟아나가는 것 까지만 조사해볼 수 있었다.

 

 실무에서 이슈들어온 고객사와 동일한 시스템 구성도 아니고, 빌드/배포 주기도 다르고, 사용자 수나 요청수등등 많은 것들이 다르기 때문에 직접적으로 메모리릭이 되는 원인을 찾고 해결하는 과정은 이어 진행할 예정이기 때문에 정리되는대로 추가 포스팅을 공유하겠습니다~🤣

 

 이번 조사는 사실상 OOM이 발생한 것도 아니고 단순히 메모리 사용량이 지정한 값보다 훨씬 상회했기 때문에 그리고 고객사에서 메모리 사용량이 점진적으로 증가했다는 내용까지만 전달받은 상태에서 의심할 수 있는 부분을 빠르게 확인해본 개념이다. 본격적으로 메모리릭으로 추정해보려면 더 많은 근거가 필요할 것으로 보이고, 메모리릭으로 강하게 의심이 되는 시점이 오면 문제가 되는 시스템 구성을 포함한 여러 환경을 유사하게 만들어서 테스트, 분석 진행할 것 같다.

 

 

 

 

 기타 

메모리 용량 단위 가늠해보기 

 

Byte / 1024 = Kb

Kb / 1024 = Mb

 

4860832Byte / 1024 / 1024 = 4.63671875MB (약 4.6MB)

대충 어느정도 크기인지만 확인하려면 0 세개씩 때내면 된다. 그리고 터미널에서 용량이 표시안된 경우는 보통 Byte를 의미한다. 1,000,000 Byte = 약 1Mb 이하.

 

 

(리눅스/맥) 터미널 명령어

 

history 명령어

history 명령어는 터미널에서 내가 입력했던 명령어 히스토리를 .bash_history 파일에 기록해서 최근에 실행한 명령어부터 역순으로 기록해준다.

$ history
    1  ssh user@remote_host
    2  ls
    3  cd documents/
    4  vim test.txt
    5  cat test.txt
    6  clear
    7  history
    
    
$ 2!
ls

다시 입력하고 싶은 명령어 번호 뒤에 느낌표를 붙여 입력하면 해당 명령어가 바로 실행된다.

 

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기