콘텐츠로 이동

SkyWalking으로 JVM 가시성 확보하기: 모범 사례


개요

JVM은 컴퓨팅 장치를 위한 사양으로, 가상의 컴퓨터를 의미하며 Java Virtual Machine의 약자입니다. Java는 추상화 수준이 매우 높은 언어로, 자동 메모리 관리 등 다양한 기능을 제공합니다. 이를 위해 JVM이라는 추상화 계층이 존재하며, JVM은 운영체제 위에서 실행되어 Java 바이트코드를 실행함으로써 Java가 플랫폼 독립성을 가질 수 있도록 합니다.

여기서는 JVM 메모리 구조와 스레드에 대해 간략히 소개한 후, SkyWalking을 사용하여 JVM 메트릭 데이터를 수집하고 Guance을 통해 가시성을 확보하는 방법을 설명합니다.

JVM 메모리 구조

Java 컴파일러는 오직 JVM을 대상으로 하며, JVM이 이해할 수 있는 바이트코드 파일을 생성합니다. JVM의 클래스 로더는 바이트코드를 로드하고, 로드가 완료되면 JVM 실행 엔진이 이를 실행합니다. 전체 클래스 로딩 과정에서 JVM은 데이터와 관련 정보를 저장하기 위해 일정 공간을 사용하는데, 이 공간이 바로 JVM 메모리입니다.

JVM 사양에 따라 JVM 메모리는 다음과 같이 구분됩니다.

  • 프로그램 카운터(Program Counter)
    다음 JVM 명령어의 실행 주소를 기록합니다.

  • 가상 머신 스택(Virtual Machine Stack)
    각 스택은 여러 개의 스택 프레임(Frame)으로 구성되며, 각 메서드 호출 시 사용되는 메모리에 해당합니다. 로컬 변수 테이블, 피연산자 스택, 상수 풀 참조 등의 정보를 저장합니다. 메서드 호출부터 실행 완료까지의 과정은 Java 가상 머신 스택에서 하나의 스택 프레임이 푸시(push)되고 팝(pop)되는 과정에 해당합니다. -Xss를 통해 가상 머신 스택 메모리 크기를 설정할 수 있습니다.

  • 네이티브 메서드 스택(Native Method Stack)
    기능과 특징이 가상 머신 스택과 유사하지만, 네이티브 메서드 스택은 네이티브 메서드를 위해 서비스합니다.

  • 메서드 영역(Method Area)
    메서드 영역은 JVM 사양이며, PermGen(Permanent Generation)과 Metaspace는 모두 그 구현 방식 중 하나입니다. JDK 8 이후로 기존 PermGen 데이터는 힙(Heap)과 Metaspace로 분할되었습니다. Metaspace는 클래스의 메타 정보를 저장하고, 정적 변수와 문자열 상수 풀 등은 힙에 저장됩니다.

  • 힙(Heap)
    힙 영역은 모든 스레드가 공유하며, 주로 객체 인스턴스와 배열을 저장합니다. 물리적으로 연속되지 않은 공간에 위치할 수 있지만, 논리적으로는 연속적이어야 합니다.
    힙 메모리는 Young Generation과 Old Generation으로 나뉩니다. Young Generation은 다시 Eden 영역과 Survivor 영역으로 나뉩니다. Survivor 영역은 FromSpace와 ToSpace로 구성됩니다. Eden 영역은 큰 용량을 차지하고, Survivor 두 영역은 작은 용량을 차지하며, 기본 비율은 8:1:1입니다.
    Java 힙에서 인스턴스 할당을 위한 메모리를 완료할 수 없고 힙을 더 이상 확장할 수 없을 경우, Java 가상 머신은 OutOfMemoryError 예외를 발생시킵니다.

  • 런타임 상수 풀(Runtime Constant Pool)
    런타임 상수 풀은 메서드 영역의 일부로, 테이블 형태입니다. 가상 머신 명령어는 이 테이블을 통해 실행할 클래스 이름, 메서드 이름, 매개변수 유형 등의 정보를 찾습니다.

  • 직접 메모리(Direct Memory)
    직접 메모리는 가상 머신 런타임 데이터 영역의 일부가 아니며, Java 가상 머신 사양에 정의된 메모리 영역도 아닙니다. Java 힙 크기의 제한을 받지 않으며, 본체의 전체 메모리 크기에 의해 제한됩니다. 직접 메모리는 -XX:MaxDirectMemorySize로도 지정할 수 있습니다. 직접 메모리 공간 할당은 더 높은 성능을 소비하지만, 직접 메모리의 I/O 읽기 및 쓰기 성능은 일반 힙 메모리보다 우수합니다. 메모리가 고갈되면 OutOfMemoryError 예외가 발생합니다.

스레드(Thread)

Java에는 두 가지 유형의 스레드가 있습니다. 사용자 스레드(User Thread)와 데몬 스레드(Daemon Thread)입니다.

  • 사용자 스레드는 시스템의 작업 스레드로 이해할 수 있으며, 애플리케이션에서 필요한 비즈니스 작업을 완료합니다.
  • 데몬 스레드는 특수한 스레드로, 가비지 컬렉션 스레드와 같은 일부 시스템 서비스를 백그라운드에서默默地 수행합니다.

JVM에서 스레드는 다음과 같은 상태를 가집니다.

  • NEW: 아직 실행을 시작하지 않은 스레드 상태입니다.
  • RUNNABLE: 실행 중인 스레드 상태입니다.
  • BLOCKED: 모니터 락을 기다리며 대기 중인 상태입니다.
  • WAITING: 다른 스레드가 특정 작업을 수행하기를 무기한 기다리는 상태입니다.
  • TIMED_WAITING: 제한된 시간 동안 다른 스레드가 특정 작업을 수행하기를 기다리는 상태입니다.
  • TERMINATED: 스레드가 종료된 상태입니다.

성능 메트릭

메트릭 설명 데이터 타입 단위
class_loaded_count
로드된 클래스 수입니다. int count
class_total_loaded_count 총 로드된 클래스 수입니다. int count
class_total_unloaded_class_count 총 언로드된 클래스 수입니다. int count
cpu_usage_percent CPU 사용률입니다. float percent
gc_phrase_old/new_count GC old 또는 new 횟수입니다. int count
heap/stack_committed 힙 또는 스택의 커밋된 메모리 양입니다. int count
heap/stack_init 힙 또는 스택의 초기화된 메모리 양입니다. int count
heap/stack_max 힙 또는 스택의 최대 메모리 양입니다. int count
heap/stack_used 힙 또는 스택의 사용된 메모리 양입니다. int count
pool_*_committed 다양한 풀의 커밋된 메모리 양입니다.
(code_cache_usage, newgen_usage, oldgen_usage,
survivor_usage, permgen_usage, metaspace_usage)
int count
pool_*_init 다양한 풀의 초기화된 메모리 양입니다.
(code_cache_usage, newgen_usage, oldgen_usage,
survivor_usage, permgen_usage, metaspace_usage)
int count
pool_*_max 다양한 풀의 최대 메모리 양입니다.
(code_cache_usage, newgen_usage, oldgen_usage,
survivor_usage, permgen_usage, metaspace_usage)
int count
pool_*_used 다양한 풀의 사용된 메모리 양입니다.
(code_cache_usage, newgen_usage, oldgen_usage,
survivor_usage, permgen_usage, metaspace_usage)
int count
thread_blocked_state_count BLOCKED 상태의 스레드 수입니다. int count
thread_daemon_count 데몬 스레드 수입니다. int count
thread_live_count 라이브 스레드 수입니다. int count
thread_peak_count 최대 스레드 수입니다. int count
thread_runnable_state_count RUNNABLE 상태의 스레드 수입니다. int count
thread_time_waiting_state_count TIMED_WAITING 상태의 스레드 수입니다. int count
thread_waiting_state_count WAITING 상태의 스레드 수입니다. int count

전제 조건

  • DataKit 1.4.17+ 설치

설정 단계

1 수집기 활성화

DataKit이 설치된 호스트에 접속하여 sample 파일을 복사합니다.

cd /usr/local/datakit/conf.d/skywalking
cp skywalking.conf.sample skywalking.conf

DataKit이 배포된 호스트와 JVM을 수집할 Jar 파일이 같은 호스트에 있지 않은 경우, skywalking.confaddress = "localhost:11800"에서 localhost를 Jar 파일이 있는 호스트의 IP로 수정해야 합니다.

이번 예제에서는 Jar 파일과 DataKit이 동일한 호스트에 있으므로 localhost를 통해 JVM 메트릭을 보고할 수 있으며, sample 파일을 수정할 필요가 없습니다.

2 애플리케이션 배포

skywalking-demo 프로젝트를 다운로드하고, Idea로 열어서 「package」를 클릭하면 skywalking-user-service.jar 파일이 생성됩니다.
Skywalking을 다운로드하고, 압축을 풀어 agent 디렉터리를 호스트에 복사하여 skywalking-user-service.jar와 동일한 디렉터리에 저장합니다.

다음 명령을 실행하여 애플리케이션을 시작합니다.

java  -javaagent:agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=skywalking-user  \
-Dskywalking.collector.backend_service=localhost:11800 \
-jar skywalking-user-service.jar

3 JVM 가시성 확보

Guance」에 로그인하여 「시나리오」로 이동한 후, "JVM"을 입력하고 「JVM Skywalking 모니터링 뷰」를 선택한 다음 「확인」을 클릭합니다.

그런 다음 새로 생성된 「JVM Skywalking 모니터링 뷰」를 클릭하면 가시성 데이터를 확인할 수 있습니다.

image

image

image

문서 평가

이 페이지가 도움이 되었나요?