ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 성능 모니터링 ②-① 본론 (VisualVM 분석 및 연동)
    JAVA공부/JAVA 2023. 9. 13. 02:39

    자바애플리케이션에서 JVM을 모니터링하기 위해 가장 많이 쓰이는 VisualVM을 연동을 하여

    어떠한 지표들을 보아야하는지, 트러블을 일으키고 해결하는 것을 실습해보도록 하자.

    여기서 애플리케이션은 스프링 부트 웹 애플리케이션을 이용하도록 하겠다.

     

     

    1. 설치

     

    VisualVM: Download 이곳에 가서 각 운영체제 맞는 VisualVM을 설치하도록 하자.

    필자는 Windows여서 Zip파일을 받아서 압축해제하였다.

     

     

    2. 실행

    압축해제를 진행하면 bin폴더에 visualvm.exe 파일이 존재한다. 실행시키고 라이센스에 동의한다.

    실행에 성공하면 다음과 같은 화면이 나오게 된다.

    현재 구동중인 자바 애플리케이션이 존재하지 않아서 Local밑에 나오는 자바애플리케이션이 VisualVM외에 존재하지 않는다.

     

    인텔리제이를 실행하고 스프링 부트 서버를 띄워놓도록 하고 다시 돌아와보면

     

    해당 서버에 대한 VM활동을 볼 수 있게 되었다.

     

    하나하나 분석 해보도록 하자.

     

    1. 좌측 메뉴 분석

     

     

    Local 

    로컬 시스템에서 실행되는 Java애플리케이션을 모니터링하고 관리할 수 있다.

    로컬에서 실행되는 Java프로세스 목록을 제공한다.

     

    ■ Remote

    원격 컴퓨터에서 실행되는 Java 애플리케이션에 연결하고 모니터링 할 수 있다.

    로컬과 동일한 모니터링 및 프로파일링 도구에 엑세스 할 수 있다.

     

    ■ VmCodeDumps 

    JVM에서 생성된 코드 덤프를 관리하고 분석하는 데 사용된다.

    특정 순간의 JVM 내부 상태에 대한 스냅샷이며 충돌, 예상치 못한 동작과 같은 문제를 진단하는데 사용한다.

     

    JFR SnapShots

    해당 탭을 클릭하면 Java애플리케이션의 JFR 스냅샷을 찍고 분석할 수 있다.

    CPU 사용량, 메모리 할당 등과 관련된 지표들을 캡처하여 분석할 수 있게 해준다.

     

    Snapshots

    힙 덤프 및 스레드 덤프를 분석하는데 사용된다.

    힙 덤프는 메모리 사용량에 대한 것을 식별하는데 사용하고

    스레드 덤프는 병목현상을 진단하는데 사용한다.

     

     

     

    2. JVM 인스턴스 분석

    좌측에 있는 실행중인 애플리케이션을 누르면 분석단계로 넘어갈 수 있다.

     

    1) OverView

     

    ● PID : Java애플리케이션의 프로세스 고유 식별자

    Host: Java애플리케이션이 실행중인 컴퓨터 또는 호스트이름

    Main Class: Java애플리케이션의 진입점이 되는 클래스

    JVM 정보: Jva가상 머신의 버전 및 구성과 관련된 정보 제공

    Heap Dump on OOME: OOM에러가 발생했을 경우 힙덤프를 생성할 것인지에 대한 여부

     

    2) Jvm arguments

     

    intellij로 스프링 부트를 띄웠을 때 자동으로 구성된 아규먼츠인데 궁금하므로 알아보자.

    agentlib:jdwp -> java 애플리케이션의 디버깅을 위한 Java Debug Wire Protocol(JDWP) 옵션이다.

    XX:TieredStopAtLevel: 티어드 컴파일러의 동작을 제어하는데 사용되며, 여러 수준의 컴파일을 다루는데 사용한다.

    1로 설정하게 되면 C2 컴파일러로 설정하게되는데 해당 컴파일러는 높은 실행성능 (최적화된 코드, 컴파일 시간이 오래걸림)

    2로 설정하게 되면 빠른 컴파일 타임, 상대적으로 덜 최적화되어있다. (실행이 빠름)

    Dspring.output.ansi.enabled: ANSI이스케이프 코드를 사용하는지에 대한 여부 (Always는 항상)

     Dcom.sun.management.jmxremote: 원격으로 JVM을 모니터링하고 관리하기 위한 JVM 시스템 프로퍼티

    Dspring.jmx.enabled: 어플리케이션에서 JMX를 사용할지에 대한 여부

    Dspring.liveBeansView.mbeanDomain: 스프링 애플리케이션에서 라이브 빈 정보를 모니터링 하기 위한

    MBean 도메인 을지정하는 환경 변수

    Dspring.application.admin.enabled: 스프링 부트 애플리케이션에서 관리자 인터페이스를 노출하는지에 대한 여부

    (스프링 부트 어드민과 관련되어있다.)

    Dmanagement.endpoints.jmx.exposure.include: 스프링 부트에서 JMX 노출을 포함시킬 엔드포인트를 설정하는 프로퍼티

    (액추에이터)

    javaagent: Java애플리케이션을 실행할 때 JVM에 대한 에이전트를 로드하도록 지정하는 JVM 옵션이다.

    에이전트는 애플리케이션의 동작을 감시하거나 변경 할 수 있다.

     

    ※ MBean: JMX 기술을 통해 관리되는 객체를 의미

     

     

     

    3) Monitor

     

    ■ CPU: CPU사용률, GC의 활동률을 볼 수 있는 대시보드이다.

    ■ Classes: 로드 되어 있는 클래스와 클래스 로더에 관한 정보를 표시한다. 힙메모리에 올라온 클래스 인스턴스에

    관한 정보가 아닌 클래스 로딩 혹은 어떤 클래스가 어떤 클래스 로더에 의해 로드되어있는지와 같은 클래스 수를 보여준다.

    ■ Heap: 시간에 따른 힙 메모리 상태를 보는 대시보드, 올라가다 줄어드는 것을 보면 GC가 일어나는 것을 확인 할 수 있다.

    Heap Size - 현재 힙 메모리 총 크키, Used Hep: 사용중인 힙 메모리 크기

    ■ Threads: 구동중인 서비스의 사용중인 스레드 수 , 데몬 스레드 수를 확인해서 성능 문제를 식별할 수 있다.

     

     

     

     

    4) Threads

    스레드 탭을 누르면 다음과 같은 화면이나온다.

    Monitor에서 보았던 라이브스레드와 데몬 스레드에 대한 내용이 나오며, 쓰레드 덤프를 뜰 수 있도록 버튼이 활성화 되어있다.

    스레드 실시간 화면

     

    여기서 테스트 해보려는 것은 톰캣 내장 서버에서 사용하는 스레드 http-nio- 인데,

    이것은 Http 요청을 처리하는 NIO(Non-blocking I/O) 커넥터를 나타낸다.

    10000은 포트번호, exec-1은 스레드풀 내에서 스레드를 식별하는 번호다.

     

     

    스레드 상태

    각 스레드 상태에 대해서 확인해보자면

    Running: 해당 스레드가 CPU에서 실행중인 상태 (애플리케이션 코드를 실행하거나, 다른 작업을 하고 있음)

    Sleeping: 스레드가 일정 시간 동안 일시 중지된 상태 (Thread.sleep() 혹은 기타 대기 시간을 설정하는 메서드에 의해 발생)

    Wait: 스레드가 특정 조건을 충족할때 까지 대기중인 상태 (Object.wait() 혹은 Thread.join()과 같은 메서드 호출에 의해 발생)

    Park: java.util.concurrent.locks.LockSupport.park() 메서드에 의해서 대기중인 상태 (락 관련 동작

    Monitor:스레드가 객체의 모니터 락을 얻기 위해 대기중인 상태, 해당 스레드가 synchronized 블록에 들어가려고 하지만

    다른 스레드가 이미 점유하고 있어 대기열에서 기다리는 상태 

     

     

    API를 하나 만들어서 호출하는 것을 해보고 무슨 차이가 있는지 확인해보도록 하겠다.

    짧은 시간에 지나갈 수 있으므로 Thread.sleep으로 해당 스레드를 잠시 재우도록 해보겠다.

     

    @RestController
    @RequestMapping("/test")
    public class CallController {
    
        @GetMapping
        public String getTestMessage() throws InterruptedException {
            Thread.sleep(10000L);
            return "test";
        }
    }

     

    jmeter로 해당 API 를 16개의 스레드로 호출해보았다.

    ※ JMeter에 관한 설명은 Jmeter 사용기 :: 코딩꿈나무 (tistory.com) 여기에서 확인해보자.

     

     

    기존 10개의 스레드풀 외에 6개가 추가로 할당되고 지정한 슬립의 시간만큼 잠들어 있는 것을 확인 할 수 있었다.

     

     

    더 많은 스레드로 호출하게 되면 어떻게 될까?

    컴퓨터 성능이 따라주지 못해서 안되겠지만 스레드 수를 100으로 하고 호출해보았다.

    --> 스레드 수가 총 100개로 생성되었다.

     

    그 이후 시간이 조금 지나자

    스레드 수를 다시 10개로 맞춰주었다. 이 때의 스레드는 초기의 스레드들이 아니라 랜덤하게 살아남았다.

    찾아보니 톰캣에서 스레드가 더이상 사용되지 않는 경우 minSpareThreads 만큼 줄여주는 작업을 한다고 한다.(핵심 기능)

    힙 메모리의 상태도 보았는데

    한번 호출한 이후로 performGC를 하였는데 메모리를 큰 폭으로 상승하였고 이내 GC가 일어났다.

    뒤에 한번 더 100개의 스레드를 호출하였는데, 이후 원래의 파형으로 돌아가지 않아서 대기하다 힙 덤프를 떠 보았다.

     

     

    GC 발생 전 Heap
    GC 발생 후 Heap

    위의 그래프에 비해 생각 보다 힙 사이즈가 크지 않았다. 계산해보니 3856바이트였다.. 근데 왜 그래프는 저렇게 나왔을까?

     

    힙 메모리는 여러가지 요인에 의해서 증가되고 감소된다.

    가비지 컬렉션

    힙 공간 조정 (메모리 요구에 따른 공간 조정)

    객체 생성 및 소멸

    캐시 및 임시 메모리

    스레드 활동

    JVM옵션 및 설정

     

    힙덤프를 여러번 누르면서 확인해보니 누를 때마다 GC를 처리하고 덤프가 떠지고 있었다...

    조금 더 알아보니 다음과 같이 jcmd를 이용하여 HeapDump를 떠야 (Java17) GC를 트리거하지 않게 된다.

    jcmd <pid> GC.heap_dump -all <filename>
    
    ##jcmd 5624 GC.heap_dump -all C:\Users\KIMTAEHYUN\Desktop\performance3

    java 자바 - 가비지 수집없이 메모리 누수를 분석하기 위해 힙 덤프를 만들 수 있습니까? - 스택 오버플로 (stackoverflow.com)

     

     

    드디어 그래프와 동일한 힙 메모리를 확인 할 수 있었다..

     

    GC가 처리되는 이유는 GC가 되는것을 제외하고 힙덤프를 띄워주는게 디버깅에 유리해서이지 않을까 싶다.

     

     

     

     

    5) Sampler

     

     

    CPU 사용률, 스레드 활동 및 메모리의 실시간 상태를 자세히 볼 수 있다.

    Sampler는 무시할 수준의 오버헤드만 발생시키므로 성능 저하가 크게 일어나지 않는다. 

    그러나 성능 테스트중이거나 초고빈도로 샘플링을 활성화하는 경우는 주의해야한다.

    샘플링 간격을 조정하는 방법으로 오버헤드를 최소화 할 수 있다.

     

    ※ 오버헤드: 어떤 프로세스나 활동을 수행할 때 추가적으로 필요한 부가적 비용, 시간, 자원, 또는 처리량을 나타내는 용어

     

    위의 CPU 탭을 클릭하면

    Cpu Samples

    메서드 호출 스택 (코드 실행 경로)를 나타내며, 성능 이슈 식별 및 호출빈도, 지속시간을 알 수 있다

    호출 스택의 상대적인 실행속도를 파악할 수 있으며, CPU 시간을 얼마나 소비하는지에 대한 비율도 알 수 있다.

    Thread CPU Time

    해당 스레드가 CPU가 사용하면서 작업을 수행하는데 소요된 시간을 나타낸다.

     

    Memory탭을 클릭하면

    Heap Histogram

    힙 메모리내에 현재 할당된 객체들에 대한 통계정보를 제공한다.

    객체 수, 메모리 사용량, 클래스 이름 등을 알 수 있다.

    Per thread allocations

    각 스레드가 수행한 객체 할당에 관한 정보를 제공한다.

     

     

     

    6) Profiler

     

    Java애플리케이션의 성능을 분석하고 최적화하는 도구이다. 애플리케이션 실행중 발생하는 다양한 이벤트와 상호작용

    모니터링하고 측정한다.

     

    VisualVM Profiler탭

    Sampler와 무슨 차이가 있나 싶었는데, Sampler는 현존하는 스레드의 상태를 모두 보여주지만 위에서 씌였다싶이

    실행중 발생하는 다양한 이벤트와 상호작용 프로파일이 시작되고 스레드가 할당되서 수행하는 이벤트가 일어나야만

    정보를 수집하는 것이다.

    실제로 Jmeter로 Call을 실행하면

     

    이렇게 되는 것을 확인 할 수 있다. 위에서 보았던 스레드의 상세 내용보다 실제 메서드 호출 스택만 추려서 확인할 수 있어

    디버깅할때 유리할 수 있다.

    Profiler는 성능 저하가 발생할 수 있기 때문에 이에 대비하여 사용해야한다.

    - 프로파일링 옵션을 최소한으로 설정 후 필요한 경우에만 확장 옵션을 활성화한다.

    - 프로파일링을 실행하기 전에 애플리케이션의 부하를 낮추는 최적의 시간대를 선택한다.

    - VisualVM을 최신버전으로 업데이트하고 환경설정을 최적화한다.

    - 프로파일리 결과를 최소한의 수집하도록 범위를 조정한다.

     

     

    메모리 쪽을 프로파일링해보려고 하면 다음과 같은 에러가 뜰 수 있다.

     

    이는 Memory Setting에서 프로파일될 클래스를 선정해줘야하는데 Cpu 세팅과 동일하게 설정하면

    패키지 경로에 해당하는 힙 메모리에 대하여 모두 접근이 가능하다..

    Memory Settings 프로파일할 클래스 설정

     

    Memory에 대해서 기존과 같은 API를 100개의 스레드로 호출해보았으나, 메모리 변경사항이 나오지않았다.

    이유인 즉슨 실제 객체 생성로직이 하나도 없었기 때문이다.

    이에 따라 임의 클래스를 만들고 해당 클래스를 10000번 생성하는 로직처리를 추가하였다.

        @GetMapping
        public String getTestMessage() throws InterruptedException {
            log.info("========>>>>> test start ");
            List<User> userList = new ArrayList<>();
            for (int i = 0; i < 100000; i++) {
                userList.add(new User());
            }
            Thread.sleep(10000L);
            return "test";
        }

     

     

     

    객체 생성 진행중

    객체 생성이 모니터링 되는 것을 확인하였으며, GC가 일어나지 않아서 해당 객체들이 생성된체로 남아있는것을 확인하였다.

     

     

     

    VisualVM Sampler  VS VisualVM Profiler

     

    Sampler

    ■ 성능 저하 정도: 낮은 성능 저하를 가지는 경량 프로파일링 도구이다.

    ■ 사용 사례: Cpu 사용량 스레드 활동을 추적하서 분석하는데 사용, 코드의 CPU 사용량이 어디 집중되는지

    성능 문제를 조사하는데 유용하다.

    ■ 프로파일링 시간: 애플리케이션에 대한 지속적 모니터링을 제공하므로 필요한 시간에 시작하여 성능 문제를 빠르게 파악한다.

     

    Profiler

    ■ 성능 저하 정도: Sampler에 비해 더 많은 성능저하가질 수 있다. 특히 메모리 프로파일링과 같은 고급 프로파일링 옵션을

    활성화 하면 많은 부하가 발생한다.

    ■ 사용 사례: 메모리 사용량 분석, 메소드 호출 그래프, 메모리 누수 식별, 스레드 분석 등 고급 프로파일링을 수행하는데 사용된다.

    (정교한 분석)

    ■ 프로파일링 시간: 필요한 경우 정확한 시간에 실행하여 성능 문제를 식별해야한다.

    (애플리케이션 부하정도에 따라 성능 저하가 많이 발생한다.)

     

     

     

    JFR SnapShots

     

    JFR은 Java 애플리케이션 모니털이 및 문제 해결을 위해 제공하는 프로파일링 및 진단 도구이다.

    애플리케이션 내에 발생하는 이벤트 및 메트릭에 대한 자세한 기록을 캡처 할 수 있다.

    그러한 내용으로 스냅샷을 분석하여 애플리케이션의 동작 성능 및 리소스 활용에 대한 알아볼 수 있다.

     

    주요 특징으로는 다음과 같다.

     

    데이터 수집

    메서드 실행, GC, 메모리 사용량, 스레드 활동, CPU 사용률, I/O작업 등에 대한 정보가 포함된다.

    스냅샷 유형

    연속 기록 - 지정된 기간 동안 지속적으로 데이터를 캡처한다. 이에 따라 시간 경과에 따른 애플리케이션 동작을 모니터링한다.

    Flight Recording Dump - 특정 시점의 JVM 현재 상태에 대한 스냅샷을 캡처한다.

    성능 급증이나 예외와 같이 특정 순산게 발생한 특정 이벤트나 문제를 조사하는데 유용하다.

    낮은 오버헤드

    매우 효율적인 이벤트 기록 메커니즘을 사용하여 프로덕션환경에서도 사용하기 적합하다.

     분석도구

    VisualVM도 JFR 분석을 제공하나, JMC(Java Mission Control)보다는 상세하게 제공하지는 않는다. (시각화 분석)

    보안 고려 사항

    JFR 스냅샷은 강력한 진단 도구지만, 민감한정보가 포함되어있어 엑세스를 보호하는 기능이 필요하다.

     

     

     

    JFR 스냅샷 파일 호출
    JFR 파일 로드시

     

    OverView ~ Threads는 위와 동일한 내용이고

    File IO 부터 보자고 한다면

     

    File I/O

    JFR 기록기간동안 파일 입출력(읽기, 쓰기)에 대한 이벤트 목록을 표시한다.

    각 I/O작업에 대해 작업 유형(읽기인지 쓰기인지), 파일 경로, 바이트 수, 이벤트 시작 및 종료시간을 알 수 있다.

     

    Socker I/O

    소켓연결에 대한 것을 캡처하며 로컬 및 원격 IP주소와 포트 , 연결시간, 읽거나 쓴 바이트 수 등에 대해 확인 할 수 있다.

     

    Exceptions

    발생한 예외, 발생한 횟수, 에러 처리 시간등을 알 수 있다.

     

    GC 

     

    맨위 뷰에서는 발생한 가비지 컬렉션의 이벤트를 알려준다.

    각 이름에 대해서 설명해보자면 G1New, Old의 같은 경우 힙메모리에 관한 설명이고

    G1 Evacutation Pause메모리를 확보하기 위해 한 영역에서 다른 영역으로 개체를 이동하는 가비지 수집 알고리즘의 단계이다.

    이 영역은 Eden, Survival 이거나  Tenured일 수 있다. 병렬성으로 진행하기 때문에 프로그램의 일시 중지 시간을 최소화한다.

    Medatadata GC ThreadShold 는 메타데이터, 클래스 메타데이터를 처리할 때 사용되는데, 메타 데이터도 메모리를 사용하고

    제대로 관리되지 않으면 메모리 사용이 과도하게 발생할 수 있기때문에 이 임계점을 설정 (또는 매개변수)를 의미한다.

    그 옆에 있는 것은 GC 발생 ID 와 가장 오래 소요된 시간, 총 소요시간으로 확인 할 수 있다.

     

    아래에 있는 뷰에서는 

    GC에 대한 설정

    Youn GC, Old GC: Old,Young영역에 대한 명칭

    Concurrent Threads: 가비지 수집 작업을 동시에 수행하기 위해 활용할 수 있는 스레드 수

    Parallel Threads: 여러 스레드를 활용하여 가비지 수집작업을 병렬화 할 수 있는 스레드 수

     

    GC 탭

     

Designed by Tistory.