개발/BE

[Java] Heap Memory 누수 분석기1 : JVM이란 ?

kkap999 2024. 2. 27. 00:52
728x90

구축해둔 모니터링 시스템 통해 간헐적으로 알림이 오기 시작했다.
서버 구동한지 한달정도 지나니 초기에 40%정도였던 힙메모리 사용률이 지속적으로 70%대를 돌파하고있었다.
왜 그런고 하니,,, 대충 이런 모양새였다
(마우스패드로 그려서 이상함)

minorGC가 일어나면서 간헐적으로 메모리 영역이 줄어들지만 메모리가 제대로 해제되지 못한(=누수가 일어나는 부분의) 객체들이 차지하는 메모리가 지속적으로 쌓이면서 평균적인 메모리 사용률이 올라가게 된 것이다.

하지만 기존에 구축되어있던 모니터링 도구로는 어떤 객체가 메모리를 잡아먹는지나,, 상세한 시간대별 힙메모리 소모율 등등은 볼 수 없었다.
그래서 모니터링 환경을 추가적으로 구축하려고 했다.
대충 알아보니 다음과 같은 방법들이 있더라

  • VisualVM
  • Heap Dump
  • Prometheus&Grafana (yml 수정 필요 = 재배포해야함)
  • jmx_exporter
  • Spring Boot Actuator
  • datadog
  • 등등 ...

하지만 이런 도구들을 무작정 때려박기보다는, 모니터링 환경이 구축되었을 때 어디를 봐야할지 알기 위해서는 JVM과 Heap 메모리 영역에 대한 이해가 필요하다고 생각했다.
그럼 let's go

JVM과 Heap 메모리 영역

자바 개발 도구인 JDK를 이용해 개발된 프로그램은 JRE에 의해 가상 컴퓨터인 JVM에서 구동된다.

https://docs.oracle.com/javase/8/docs/technotes/guides/desc_jdk_structure.html

JVM이란?

JVM은 하나의 가상 컴퓨터라고 생각하면 편하다.
실제 컴퓨터처럼 명령어의 집합을 가지고 있으며, JVM이 띄워진 실제 컴퓨터의 메모리를 조작하는 기능을 수행한다.

JVM은 Java 자체에 대해서는 알지 못하고, .class 파일 형식만을 알고있다.
.class에는 JVM의 명령어나 바이트코드 등과 같은 정보들이 포함되어있다.
작성한 java 언어는 컴파일러를 통해 JVM의 어셈블리어로 변환된 뒤, JVM이라는 가상 컴퓨터 머신 위에서 실행되는 것

그래서 kotlin, groovy 등의 언어들 또한 .class로 컴파일이 가능하기 때문에 JVM 기반 언어라고 할 수 있는 것이다.

JVM의 메모리 관리 방식

JVM은 실제 물리 머신에서 특정한 메모리 크기를 할당받는다.
그리고 내부에서 나름대로의 메모리 관리 전략을 사용하면서 조정을 하게 된다.

JVM 메모리 영역 중에 JVM이 관리할 수 있는 영역이 Heap Memory 영역, JVM이 물리적 머신에서 차지하고 있는 영역이지만 직접적으로 관리할 수 없는 물리 머신의 OS에 의해 조절되는 메모리 영역이 Non-Heap Memory=Off-Heap Memory=Native Memory 라는 영역이다.

Heap Space의 구조

  • virtual : 예약되기만 하고 실제 물리 메모리에는 할당되지 않은 영역
  • young(generation) 영역
    • EDEN : 객체 최초 생성 시 들어가는 영역
    • Survivor0/1 : 두 개의 영역으로 이루어지며, 하나는 항상 비워져있고 하나는 채워져있다.

JVM에는 기본적으로 객체들이 저장되며, 기동 시 설정한 -Xms 크기에서 시작되어 -Xmx 크기까지 커질 수 있다.
힙 영역 사이즈는 JVM 실행 옵션으로 줄 수 있다.
이 힙 공간에 객체를 쌓아가다가, 객체가 도달 불가능한 쓰레기 상태가 되면 Garbage Collector에 의해 정리되는 방식

더보기

💡 Garbage Collect 방식
가장 간단한 방법은 모든 객체에 대해서 매번 도달 가능한 상태인지 체크하는 방식 = O(N)
스캔 방식도 비효율적이며, 객체 수가 많아질수록 시간도 정비례해서 증가하기 때문에 대규모 어플리케이션에는 적절하지 않은 방식이다.
JVM은 다양한 가비지 콜렉션 방식을 결합한 generational collection이라는 방식을 사용

generational collection 방식이란?
비어있는 Survivor을 제외한 young 영역이 전부 차면, JVM이 멈추고(suspend) stop-the-world 상태로 들어간다. EDEN과 Survivor를 검토해 살아있는 녀석을 비어있던 Survivor로 옮긴다. 이를 minorGC라고 한다.
minorGC를 할 때, young에서 오래 남아있던 녀석들은 old로 옮기는데, 이 작업을 반복하다가 Old 영역도 가득 차게 되면 정리해주는 작업이 필요하다. 이를 majorGC라고 하며, 관련 오브젝트가 많기 때문에 minorGC보다 오래걸린다.

 

Non-Heap Space의 구조

JVM이 관리하지는 않지만, JVM이 사용하는 영역

  • MetaSpace : 클래스의 메타데이터를 저장하는 영역. Java8 이전까지는 힙 영역에 존재했었다.
    • Class Space : 클래스 관련 메타데이터, virtual method나 인터페이스의 메타데이터를 저장하기도 한다.
      GC에서 사용할 메모리 레퍼런스가 어떻게 되는지 저장하기도 함(OopMap)
    • Non-Class Space : 상수 풀, virtual method(오버라이드 가능한) 메소드가 아닌 클래스 메타데이터, 메소드 카운터, 어노테이션 메타데이터 등을 포함한다.
  • Thread Memory(Stack Memory) : 아직 실행이 안끝난 메소드와 그 메소드의 로컬 변수 등이 포함
  • Code Cache
    JVM 기반 어플리케이션이 동작하는 과정에서 컴파일된 바이트코드를 캐싱하는 역할
    • JVM에서 bytecode로 컴파일됨
    • bytecode를 machine 코드로 JVM interpreter가 바꿔줌
    • JIT compiler가 런타임에 최적화 추가로 더 해줌
  • Garbage Collector Data : GC 알고리즘에 관한 다양한 데이터구조와 병렬처리를 위한 스레드 정보

 

참고링크들

 

JVM과 GC 방식에 대해서는 추후에 좀 더 깊게 공부해보려고 한다.

오늘의 교훈
- 모니터링 환경은 잘 구축해두자

 

다음 글에서는 모니터링 환경 구축기가 이어집니다.