Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

DevYGwan

JVM Memory의 3가지 메트릭 지표 - Committed Memory란? 본문

Study/Spring

JVM Memory의 3가지 메트릭 지표 - Committed Memory란?

YGwan 2024. 4. 2. 09:12

요즘 들어 서버 유지/보수에 관심이 생겨 모니터링 하는 방법에 대해서 공부를 했습니다. 모니터링은 크게 " 데이터 수집 -> 통합 -> 시각화 " 로 이루어져 있습니다. 이를 구현할 수 있는 여러가지 기술이 있지만, 제가 사용한 방법은 Actuator, Promethues, Grafana를 사용해서 모니터링을 하는 방법을 선택했습니다. 제가 이 방법을 선택한 이유는,

  1. Spring Actuator가 스프링에서 공식으로 지원하는 라이브러리이다.
  2. 대표적인 Spring 모니터링 라이브러리 중 하나이다.
  3. 관련 자료들이 많다.

등이 있습니다. 저는 대부분 어떤 기술을 선택할 때, 신뢰할 수 있는 자료인가를 젤 먼저 확인하고 이 후 자료들이 많은지를 기준으로 선택하는 것 같습니다. 물론 자료가 없다면, 공식 문서나 라이브러리 등을 확인하면서 찾기도 하구요.

그렇다면, 간단하게 Actuator, Promethues, Grafana를 설명하고 모니터링을 하던 도중 JVM Memory 관련해서 흥미로운 주제가 있어 이를 설명하도록 하겠습니다.

 


Server Monitoring이란?

 서버 모니터링이란, 컴퓨터 네트워크에서 서버의 성능, 상태 및 작동 여부를 지속적으로 추적하고 검사하는 프로세스를 말합니다. 단순히 기능 구현 위주로 하는 시대는 이제 끝났다고 생각합니다. 이제는 구현을 넘어서 성능 & 장애 대비 등을 생각해서 모든 기능을 작성해야 된다고 생각합니다. 장애를 대비하기 위한 방법은 많지만, 아무리 장애를 대비하더라도 장애는 언제든 발생 할 수 있습니다. 따라서 장애가 발생한 것을 미리 파악하고 이를 대처할 수 있는 모니터링의 중요성이 강조되고 있다고 생각합니다. CPU 사용량, 메모리 사용량, 요청 & 응답 시간 등 여러 키워드를 가지고 모니터링을 진행한다면, 장애가 생기기 전에 문제를 빠르게 파악하고 해결 할 수 있기 때문입니다. 실제로 기능 구현보다, 기능 구현 완료 후 배포까지, 배포 이후 운영까지... 이러한 작업이 헐씬 더 시간이 많이 걸립니다. 따라서 이러한 모니터링 시스템을 구축하고, 얻은 지표들을 바탕으로 계속해서 감시하는 것이 매우 중요합니다.

그렇다면 제가 선택한 모니터링 기술인 Actuator, Prometheus, Grafana에 대해서 간단히 설명하도록 하겠습니다.

 

Actuator 

 Actuator는 스프링 프레임워크에서 제공되는 라이브러리 중 하나로, 애플리케이션의 상태를 모니터링 할 수 있고, 메트릭 수집을 위한 Http Endpoint를 제공해줍니다. 대표적으로 health, metrics, prometheus 등의 endpoint를 활성화 할 수 있으며 각각에 맞는 데이터 정보를 제공해줍니다. 이 중 promethues endpoints의 경우, 애플리케이션의 메트릭 데이터를 수집하는 데 사용되며 Prometheus 서버는 이 엔드포인트에 주기적으로 요청하여 메트릭 데이터를 수집합니다.

 

Prometheus

 Prometheus는 오픈 소스 모니터링 시스템 및 경고 도구로, 다양한 작업에서 발생하는 메트릭을 수집하고 저장하는 데 사용됩니다. 애플리케이션에서 발생한 메트릭 정보를 매번 바로 바로 모니터링 할 수도 있지만, 대부분 모니터링 시스템의 경우 발생한 메트릭 정보를 그 순간 뿐만아니라 이전 데이터까지 포함하여 그래프 형식으로 보여주는 것이 일반적입니다. 그러기 위해서는 메트릭 데이터를 저장해야되는데, 이를 프로메테우스가 제공합니다. 프로메테우스는 메트릭을 지속적으로 수집하고 이를 자체 DB에 저장합니다. 즉, 하나의 독립적인 서버라고 생각하시면 이해하기 편합니다. 

 

Grafana

 Grafana는 다양한 데이터 소스에서 수집된 데이터를 시각화하고 모니터링하기 위한 오픈 소스 플랫폼입니다. Prometheus, InfluxDB, Elasticsearch 등 다양한 데이터 소스와 연동이 가능하고 이 데이터들을 하나의 대시보드에서 볼 수 있도록 지원합니다. 여러 패널을 포함하는 대시보드를 생성해, Time Series, Bar Chart, Gauge 등 다양한 시각화 도구를 제공해 사용자가 빠르고 쉽게 데이터 들을 확인할 수 잇도록 강력한 시각화 대시보드를 제공합니다.

 


 

그렇다면 지금부터 모니터링 도중 개인적으로 흥미로웠던 메트릭 데이터 관련해서 설명드리도록 하겠습니다.

 

※  JVM Memory

기본적으로 JVM Memory 관련 데이터를 Actuator에서 제공합니다. Memory는 크게 heap영역과 Non heap 영역으로 나뉘는데 이는 일반적으로 많이 알고 있는 사실일 겁니다. 실제로 이 두개를 area 라는 Tag 필터를 가지고 각각 모니터링 할 수 있습니다. (area = "heap" or area = "nonheap") 그런데,  prometheus에서 memory 관련 데이터 정보 중 JVM Memory 관련 메트릭 데이터 중 사용을 기준으로 크게 3가지로 나뉘는 것을 확인할 수 있었습니다.

 

1.  jvm_memory_max_bytes

The maximum amount of memory in bytes that can used for memory management

 memory 관련 데이터 정보 중 첫번째는 "jvm_memory_max_bytes" 입니다. 이 메트릭은 JVM이 최대로 사용할 수 있는 메모리의 한계를 나타냅니다. 즉, JVM이 실행 중인 환경에서 최대로 할당할 수 있는 메모리 양입니다. 이 값은 JVM 시작 시 또는 실행 중에 명시적으로 구성되며, JVM이 실행 중에 변경되지 않습니다.

 

2.  jvm_memory_committed_bytes

The amount of memory in bytes that is committed for the Java Virtual Machine to use

 memory 관련 데이터 정보 중 두번째는 "jvm_memory_committed_bytes" 입니다. 이 메트릭은 JVM이 실행 중인 프로세스에게 현재 커밋(할당)된 메모리 양을 나타냅니다. 이는 JVM이 실제로 사용하지 않더라도 JVM이 시스템에 요청한 메모리 양을 나타냅니다. 이 데이터 정보가 필요한 이유는 JVM은 초기에 메모리를 할당하고, 이후에도 필요에 따라 동적으로 메모리를 추가로 할당하는 식으로 동작하기 때문입니다. committed memory 크기는 항상 사용된 크기보다 크거나 같습니다.

 

3.  jvm_memory_used_bytes

A amount of used memory

 memory 관련 데이터 정보 중 마지막은 "jvm_memory_used_bytes" 입니다. 이 메트릭은 현재 JVM이 사용 중인 메모리의 양을 나타냅니다. 즉, JVM이 실행 중인 프로세스에서 현재 실제로 사용 중인 메모리 양입니다. 이 값은 JVM이 실행 중인 환경에 따라 동적으로 변할 수 있습니다. 

 

JVM의 메모리 사용 형태를 도식화하면 아래와 같습니다. 메모리 용량은 used <= committed <= max 순 입니다. 만약 used memory가 committed memory, max memory보다 높게 되면 OOM(Out Of Memory) 에러가 발생합니다. 

 


 

그렇다면, 모니터링 시스템에서 이를 확인할 때 어떤식으로 동작하는지 이미지와 함께 추가적으로 설명하도록 하겠습니다. jvm_memory_max_bytes의 경우 약 2.147 GB 입니다.

 

1.  초기 상태

  • Commited Memory가 Used Memory보다 큰 것을 확인할 수 있습니다. (차이가 그렇게 많지 않음)
  • Heap Usage의 경우 Used Heap Memory / Max Heap Memory 인데, 실제 사용량이 1.5% 정도 되는 것을 확인할 수 있습니다. (서버, 환경마다 다르다.)

 

2.  Heap에 데이터가 어느정도 쌓인 후

 이를 테스트하기 위해, List에다 계속해서 데이터를 축적하는 식으로 테스트를 진행했습니다. 이 후 OutOfMemory Exception이 발생하면, 에러 로그가 발생하도록 로직을 구현했습니다.

   private static final int ONE_MB = 1024 * 1024;
   private final List<byte[]> testList = new ArrayList<>();

    public int memoryTest() {
        try {
            for (int i = 0; i < 100; i++) {
                byte[] bytes = new byte[ONE_MB];
                testList.add(bytes);
            }

        } catch (OutOfMemoryError e) {
            log.error("Out of Memory!");
        }

        return testList.size();
    }

 

  • 동적으로 Commited Memory, Used Memory가 변하는 것을 확인할 수 있습니다. (Committed Memory > Used Memory)
  • Max Memory 양은 고정되어 있고 Used Memory의 양이 증가했기 때문에 실제 Heap Usage 퍼센트 또한 증가한 것을 확인 할 수 있습니다.

 

3.  Heap에 데이터가 꽉찬 후

 Heap에 데이터가 꽉 차게 되면 Out Of Memory가 발생합니다. 저같은 경우 이 에러를 try catch 문으로 잡았지만, 실제 이러한 에러가 발생한다면 요청에 대한 응답이 제대로 전달되지 않기 때문에 엄청난 서비스 에러가 발생할 것입니다. 이러한 문제를 " 메모리 누수 " 라고 합니다. 이러한 문제를 막기 위해서는 일단 제대로 된 데이터 처리가 되어야하며, 지속적인 모니터링 & 에러 로그 관리 등을 통해 문제 상황을 빠르게 파악하고 이를 해결할 수 있도록 해야 합니다.

  • 동적으로 Commited Memory, Used Memory가 변하더라도 Max Memory 값을 넘을 순 없는 것을 확인할 수 있습니다.
  • 아래와 같이 이렇게 꽉 찬 이후에 데이터를 추가하려고 한다면, Out Of Memory Exception이 발생하는 것을 확인할 수 있습니다.

 

 

※  Committed Memory의 역할

 이렇게 Memory를 사용 기준으로 3가지 영역으로 나눌 수 있는 것을 확인할 수 있었습니다. 저는 이걸 보고 그럼 Committed Memory의 역할이 뭔지 의문이 들었습니다. 굳이 왜 이 영역을 두었는지에 대해서 의문이 들어 이를 확인하기 위해 이번에는 메모리 사용량 증가 폭을 줄여 확인해보았습니다.

 

1.  초기 상태

 

2.  메모리 사용량 증가를 이전과 다르게 조금만 증가

  • Committed Heap Memory의 경우 초기 상태와 같이 68.157MB로 동일하다.
  • Used Heap Memory의 경우 증가한 것을 확인할 수 있다. (38.201MB -> 43.016MB)

 

메모리 사용량이 증가할때, 이미 할당된 메모리의 양(Committed Memory)이 이미 사용된 메모리(Used Memory) + 증가한 메모리 사용량 보다 클 경우 추가적인 메모리 할당이 되지 않는다.

 


 

결론

 JVM 메모리는 크게 heap과 non heap 영역으로 나눌 수 있고 이 나뉘어진 영역에서 사용을 기준으로 크게 Max & Min Memory 양, Committed Memory 양, Used Memory 양 3가지로 나눌 수 있는 것을 확인할 수 있었습니다. 기본적으로 Max & Min Memory 양의 경우 고정적으로 할당되는데, Committed & Used Memory 양은 동적으로 할당됩니다. 메모리 사용량이 증가하면, committed 도달할 때까지 Used 용량이 점차 증가하는데, Committed에 도달시 메모리를 추가 할당하기 위한 시스템 부하가 발생하는 것을 확인할 수 있었습니다. 처음에는 Committed Memory가 있는 이유가 모호했습니다. 하지만 위와 같은 테스트와 분석 결과 다음과 같이 정리할 수 있었습니다.

 

Committed Memory의 역할

JVM이 초기에 메모리를 할당함으로써, 애플리케이션이 메모리를 동적으로 할당하고 해제하는 데 필요한 오버헤드를 줄일 수 있습니다. 기본적으로 많은 메모리 사용량 증가가 발생하지 않으면, 추가적인 메모리를 할당하는 것이 아닌 이미 가지고 있는 여유 메모리 자원을 활용하여 추가적으로 메모리를 할당하는 등의 오버헤드를 줄여 성능 향상을 기대할 수 있습니다. 이는 결국 JVM은 애플리케이션이 필요로 할 때 메모리를 즉시 사용할 수 있도록 보장할 수 있다는 장점이 있습니다.