이번 콘텐츠에서 소개드릴 내용은 APM에 새롭게 추가된 ‘Agent별 리소스 대시보드’입니다. 오랜만에 선보이는 신규 화면인 만큼, 기획 단계에서 여러 고민을 거쳐 완성도를 높이기 위해 공들였습니다. 해당 기능이 현업에서 더 적극적으로 활용되기를 기대하는 마음으로 상세히 소개드리고자 합니다.
한 화면에서 보는 Java 리소스 인사이트
먼저, 이번 리소스 대시보드는 APM 프로젝트 중 Java 플랫폼에서만 제공되는 기능입니다. 이는 본 화면이 Java의 Memory Pool 데이터를 기반으로 구성되어 있기 때문입니다. 다른 언어의 에이전트에서는 해당 데이터를 수집할 수 없어 현재는 Java 프로젝트에 한해 이 기능을 지원하고 있다는 점 참고 부탁드립니다.
이번 대시보드는 다양한 데이터를 활용하고 있지만 핵심은 Java Memory Pool 데이터입니다. 메모리 풀이 시간에 따라 어떻게 변화하는지, 어떤 영역의 메모리 사용량이 높은지, GC가 발생했음에도 Eden Space 영역의 변화가 두드러지는 이유는 무엇인지 등을 시각적으로 파악할 수 있도록 설계했습니다.
이번 화면을 설계하면서 가장 많이 고민한 부분은, 그동안 제공된 화면들이 숙련자의 시각으로 구성되어 있어 처음 접하는 사용자에게는 다소 어렵게 느껴졌을 수 있다는 점입니다. 또한, 분석 과정에서 화면 간 이동이 잦거나 팝업이 과도하게 발생해 사용자 경험이 좋지 않다는 점도 개선이 필요하다고 느꼈습니다.
그래서 이번 기획에서는 이러한 불편을 최소화하고, 하나의 화면에서 주요 지표를 종합적으로 확인하며 인사이트를 도출할 수 있는 구조 즉, 사용자가 이곳저곳을 오가지 않고도 한 곳에서 문제의 원인을 빠르게 판단할 수 있는 화면을 만드는 것을 목표로 진행했습니다.
위에 보시는 화면이 이번에 새로 개발된 리소스 대시보드입니다. 이 화면은 하나의 애플리케이션 단위로 분석할 수 있도록 설계되었습니다. 따라서 프로젝트별, 애플리케이션 업무별로는 분석이 불가능하다는 점 참고 부탁드립니다.
시각적 흐름을 고려한 화면 설계
다음은 화면 구성에 대한 설명입니다.
사람의 눈은 일반적으로 화면을 볼 때 좌측 상단에서 우측 하단 방향으로 이동한다고 알려져 있습니다. 이러한 시각적 흐름을 고려해 가장 핵심적인 데이터인 ‘Java 메모리 영역’을 화면의 좌측 상단에 배치했습니다. 그리고 중요 지표인 OldGen GC Count와 ObjectPendingFinalizationCount는 대각선 아래에 배치해 시선 흐름에 따라 자연스럽게 이어지도록 구성했습니다.
특히, OldGen GC의 발생은 시스템에 위험이 감지되었음을 나타내는 중요한 신호입니다. OldGen GC는 Full GC 또는 Major GC라고 불리며, 이 현상이 발생되면 1) 응답시간의 지연, 2) 메모리 부족 위험, 3) CPU 사용량 증가와 같은 문제가 동반될 수 있습니다. 따라서 OldGen GC는 사전에 방지하는 것이 매우 중요하며, 이를 통해 시스템의 안정성과 성능을 유지할 수 있습니다.
ObjectPendingfinalizationCount는 GC에서 아직 Finalization을 수행하지 않은 객체의 수를 의미합니다. 이 수치가 높다는 건 GC 대상 객체가 많다는 걸 의미하며, 이는 GC 처리 시간의 지연으로 이어질 수 있습니다. 또한, 이로 인해 Finalizer Thread 병목 현상이 발생할 경우 시스템 전체의 성능 저하를 초래할 수 있습니다.
만약 해당 지표에서 지속적으로 10,000건 이상의 높은 수치가 확인이 된다며 코드 내 finalize() 매서드 사용 여부를 점검해보시기 바랍니다. 가능하다면 해당 매서드의 사용을 지양하고, 자원을 명확히 해제하는 방식으로 리소스 관리를 수행하는 것이 좋습니다.
자바 메모리 영역(Java Memory Pool)이란?
Java에서 Memory Pool(메모리 영역)은 JVM이 애플리케이션 실행을 위해 사용하는 메모리 영역을 의미하며, 용도에 따라 나누어 관리됩니다. JVM 마다 약간의 차이가 있을 수 있지만, 일반적으로 Heap 영역과 Non-Heap 영역으로 구분됩니다.
참고로, JDK는 OpenJDK, Oracle JDK, GraalVM, IBM Semeru JDK, Azul Zing 등 다양한 종류가 있으며, 각각의 Memory Pool 구조에는 차이가 있습니다.
1. OpenJDK / Oracle JDK의 Memory Pool 구조
Memory Pool
영역
설명
EdenSpace
Heap
새롭게 생성된 객체가 저장되는 공간
Survivor Space
Heap
Eden에서 살아남은 객체가 저장되는 공간
Old Generation
Heap
오래 살아남은 객체가 저장되는 공간
Metaspace
Non-Heap
클래스 메타데이터 저장 (JDK 8 이후)
Compressed Class Space
Non-Heap
클래스 정보를 압축하여 저장
Code Cache
Non-Heap
JIT(Just-In-Time) 컴파일된 코드 저장
2. GraalVM의 Memory Pool 구조
Memory Pool
영역
설명
Heap (Young, Old)
Heap
일반 JVM과 동일한 Heap 구조
Native Heap
Heap
AOT 컴파일 시, 네이티브 코드 저장
Metaspace
Non-Heap
클래스 메타데이터 저장
Code Cache
Non-Heap
Graal JIT 컴파일된 코드 저장
3. IBM Semeru JDK의 Memory Pool 구조
Memory Pool
영역
설명
Nursery (Young Generation)
Heap
Eden + Survivor 역할
Tenured Space
Heap
Old Generation 역할
Class Storage
Non-Heap
Metaspace와 유사한 역할 (클래스 저장)
JIT Code Cache
Non-Heap
JIT 컴파일된 코드 저장
4. Azul Zing & Azul Zulu JDK의 Memory Pool 구조
Memory Pool
영역
설명
EdenSpace
Heap
새롭게 생성된 객체가 저장되는 공간
Survivor Space
Heap
Eden에서 살아남은 객체가 저장되는 공간
Old Generation
Heap
오래 살아남은 객체가 저장되는 공간
Code Cache
Non-Heap
Code Cache
Metaspace
Non-Heap
클래스 메타데이터 저장
주요 지표 위젯
Memory
전체 메모리 사용률(%)을 표시합니다. Eden, Old Gen, Metaspace 등 내부 구성을 종합한 총 사용량입니다.
CpuTime
JVM이 애플리케이션 코드를 실행하는 데 소요된 CPU 시간을 밀리초(ms) 단위로 나타냅니다.
GCCount, GCOldgenCount
GCCount는 일정 주기 내 발생한 GC 횟수, GCOldgenCount는 Old 영역을 대상으로 한 GC 횟수만 별도로 집계합니다.
GCTime
Garbage Collection에 소요된 시간을 ms 단위로 표시합니다.
Cpu
JVM 프로세스의 CPU 사용률(%)을 나타냅니다.
TPS
초당 트랜잭션 처리 횟수를 표시합니다.
FileDescriptor
열려 있는 파일이나 소켓 등 현재 사용 중인 파일 디스크립터 수를 나타냅니다.
ObjectPendingFinalizationCount
GC가 처리해야 할 객체가 finalize() 대기 상태로 남아 있는 개수를 표시합니다.
“실시간 대시보드” 설명
최근 10분간의 추이를 바탕으로 현재 메모리 변화, Full GC 발생 여부, 특정 메모리 영역의 과도한 사용 여부 등 다양한 디표를 한눈에 확인하고 판단할 수 있습니다. 또한, 화면 오른쪽 상단에는 3개의 주요 버튼이 배치되어 있습니다.
1. 힙 히스토그램
관련 정보는 [ 인스턴스 성능관리 > 힙 히스토그램 ] 메뉴에 있는 데이터 입니다.
현재 힙 메모리에 상주하는 Java 클래스 정보를 분석하고, 메모리를 가장 많이 차지하는 클래스 TOP 100을 조회할 수 있습니다.
과도하게 메모리를 점유하는 클래스가 있는 경우 메모리 누수 가능성을 의심할 수 있습니다.
타입 설명
타입을 보시면 대문자 I , 대문자 Z , [ , Ljava/lang/String 이런것들이 보이는데 이 부분에 대해 설명 드리겠습니다. 이런 타입에 대한 부분은 JVM 내부 표현 방식이라고 이해 하시면 됩니다. 아래 표를 봐주세요.
일반적으로 개발할때 Object를 만들어서 사용하게 되는데요. 예를 들어 패키지명이 io.whatap.Timeutil 이라고 하면 JVM은 io/whatap/Timeutil 로 인식하게 됩니다. 대부분 고객이 만든 Class들은 설명드린 방식처럼 변환되고, 그 외 타입들은 아래와 같이 표현됩니다.
와탭에서는 일반적인 Class에 대해서는 보기 편하도록 “/” → “.” 으로 치환해서 보여드리고 있습니다. 그 외의 경우에는 JVM이 인식한 타입 형태 그대로 표시됩니다. 그 이유는, 일반적인 JAVA 개발에서는 큰 의미가 없을 수 있지만 리플렉션, JNI , 바이트코드 분석 등을 할 때는 관련 타입 정보를 알아두면 유용하기 때문입니다.
일반적인 타입
JVM이 인식하는 타입
boolean
Z
byte
B
char
C
short
S
int
I
long
J
float
F
double
D
void
V
int[]
[I
double[]
[D
String
Ljava/lang/String;
2. 액티브 트랜잭션
관련 정보는 [ 대시보드 > 액티브 트랜잭션 ] 메뉴에 있는 데이터 입니다.
실행 중인 트랜잭션 목록을 조회하고, 각 트랜잭션이 현재 어떤 작업을 수행 중인지 확인 할 수 있습니다. 몇 시에 시작했는지, 현재까지의 경과 시간은 얼마나 되었는지, 그리고 SQL이 실행 중인지, HTTP 호출 후 대기 중인지 등의 상태를 확인할 수 있습니다.
이 데이터를 함께 봐야 하는 이유는 메모리가 갑자기 튀거나 CPU 사용률이 높아지는 경우, 현재 수행 중인 특정 트랜잭션들이 원인일 가능성이 높기 때문입니다.따라서 이러한 관련 정보를 확인하는 것이 꼭 필요합니다.
CASE
설명
CPU가 높을 때
CPU를 높일 수 있는 이유는 다양합니다. 크게 4가지 정도로 분류가 될 듯 합니다.
[1] 연산작업, 정렬, 암호화, 데이터 압축/해제 [2] 과도한 스레드 생성 및 동기화 [3] GC에 의한 과부하 [4] I/O 연산 병목에 따른 과부하
>> 확인 하는 방법 << - 액티브 트랜잭션을 클릭하고 현재 수행 중인 트랜잭션의 콜 스택 정보를 확인합니다. - 콜 스택 정보를 확인하고 관련하여 위와 같은 일을 하는 Class가 계속 돌고 있다면 관련건에 대한 튜닝이 필요합니다. - 현재 액티브 트랜잭션에 걸리는 트랜잭션 수가 많다는 것은 그만큼 스레드가 많이 생성되었다는 의미도 포함됩니다.
Heap 메모리가 높을 때
힙 메모리가 늘어나는 이유는 단순합니다. JAVA 안에서 메모리에 뭔가를 계속 담고 있다가 단기 메모리 Minor GC를 해도 다른 무언가에 연결되어 있어서 제거되지 않고 있다면 CASE가 많이 발생되며 Heap이 늘어나는 것입니다.
사실상 메모리에 담겨 있다면 그 부분이 Cache가 아니라면 Client에 데이터를 주고 Clear를 하거나 해야 합니다. 그런데 이건 Close 처리가 사실상 사람이 개발하는 부분이라 놓치는 경우가 있으니 이점 유의하시기 바랍니다.
>> 확인 하는 방법 << - 콜 스택을 확인하고 현재 관련 비즈니스 로직이 많이 수행되고 있다면 해당 로직 확인이 필요 - SQL이 수행되고 그 스택에서 뭔가 Loop가 많이 돌고 있다면 (매칭 조건에 의해 Heap 메모리 증가) - Http Call 수행 중이고 그 스택에서 뭔가 Loop가 많이 수행되는 경우 (받고 있는 응답의 크기가 큰 경우)
3. Runnable 스레드
관련 정보는 [ 인스턴스 성능관리 > 스레드 목록/덤프 ] 메뉴에 있는 데이터 입니다.
CPU 사용량이 높아지거나 응답이 지연되는 경우 어떤 스레드가 문제를 일으키는지 파악하는 데 유용합니다. 그리고 ‘스레드 CPU 시간’순으로 내림차순 정렬이 되어 있기 때문에 현재 애플리케이션 기동 이후 부터 현재 까지 어느 스레드에서 CPU를 많이 사용하는지 확인 가능합니다.
현재, 갑자기 CPU 사용이 증가 되고 있을때 관련 화면에 들어와서 스레드 덤프 기능을 활용한다면 현재 수행중인 Thread 정보와 스택까지 함께 볼 수 있어 유용합니다.
“히스토리 VIEW” 설명
최대 3시간까지 조회가 가능하도록 개발했습니다. 그 이유는 이 메뉴는 실시간 대시보드로서 강점을 가진 화면이기 때문입니다. 그렇기 때문에 히스토리 조회 기능은 최소한으로 제공하고, 이후 별도의 리소스 분석 화면을 개발할 계획으로 기획을 진행하고 있습니다.
히스토리 VIEW는 분석의 개념이 약간 들어가 있는 화면입니다. 실시간 화면에서 확인했던 추이 데이터는 물론, 트랜잭션 / SQL / HTTPC 통계 데이터도 함께 조회할 수 있도록 구성되어 있습니다.
1. 트랜잭션 통계
관련 정보는 [ 통계 > 트랜잭션 ] 메뉴에 있는 데이터 입니다.
데이터 자체는 동일하지만, 이 화면에서는 조회 구간 동안 수행된 트랜잭션 통계를 다양한 정렬조건(건수, 에러, 평균 메모리 할당량, 평균 시간, 최대 시간)으로 볼 수가 있습니다. 이렇게 정렬해서 보는 이유는, Top 30 항목에 동일한 트랜잭션이 여러 조건에서 반복적으로 포함된다면 해당 트랜잭션이 문제 유발 트랜잭션 일 가능성이 높기 때문입니다.
여기서 평균 메모리 할당량의 값이 보이지 않는다면 옵션 확인이 필요합니다. 단 이 옵션은 TPS가 너무 높은 애플리케이션에서는 사용을 권장하지 않으니 참고 바랍니다.
평균 TPS 300 이하에서 옵션을 적용하는걸 권장드립니다.
옵션명 (기본값 false)
trace_malloc_enabled=true
2. SQL 통계
관련 정보는 [ 통계 > SQL ] 메뉴에 있는 데이터 입니다.
SQL을 보는 이유는 이유는 리소스 중 Heap 메모리와 밀접한 연관이 있기 때문입니다. 일반적으로 SQL을 호출해서 나온 데이터를 사용하여 ‘패치’라는 작업을 수행합니다. 패치란 ResultSet에 담긴 데이터를 Loop를 통해 메모리에 적재하는 과정을 의미합니다. 따라서 패치 건수가 많다는 것은 그만큼 메모리를 많이 사용하는 작업이라는 뜻입니다.
3. HTTP Call 통계
관련 정보는 [ 통계 > HTTP 호출 ] 메뉴에 있는 데이터 입니다.
HTTP Call 을 하게 되면 대부분 Response를 받게 됩니다. 요즘은 관련 응답이 JSON이 많은데 JSON도 MiB 단위로 넘어 오는 경우가 많기 때문에 관련 Call이 많다면 이 부분으로 인해 Heap이 올라 갈 수도 있습니다.
글을 마치며
이번에 새롭게 선보인 ‘Agent별 리소스 대시보드’는 Java 기반 시스템 운영 환경에서 메모리와 리소스 사용 현황을 직관적으로 파악하고, 장애 징후를 조기에 포착할 수 있도록 설계되었습니다.
시스템 성능에 민감한 현업 환경일수록 이러한 리소스 기반의 인사이트는 빠르고 정확한 대응에 있어서 큰 차이를 만들어 낼 것으로 보이는데요. APM의 새로운 리소스 대시보드를 활용하여, 하나의 화면에서 더 깊이 있는 모니터링 경험을 하시기를 바랍니다.