JVM 상태 모니터링의 핵심 요소
Java 애플리케이션의 안정성과 성능을 확보하기 위해 주요 지표를 실시간으로 관찰해야 합니다. 주요 모니터링 항목은 다음과 같습니다:
- 메모리 사용 현황 (힙 및 비힙 영역)
- GC(Garbage Collection) 활동 빈도와 지속 시간
- 메모리 누수 원인 분석
힙 메모리 구성 분석
다음은 주요 메모리 영역의 현재 및 최대 크기를 확인하는 방법입니다.
| 영역 | 최대 용량 (확인 방법) | 현재 사용량 | 경고 기준 |
|---|---|---|---|
| 전체 힙 | jmap -heap <PID> → MaxHeapSize | eden + survivor + old 영역 합계 | 사용자 설정 |
| 비힙 영역 | perm + native 영역 합계 | – | 해당 없음 |
| Eden 영역 | @Eden Space::capacity | @Eden Space::used | 해당 없음 |
| Survivor 0 | @From Space::capacity | @From Space::used | 해당 없음 |
| Survivor 1 | @To Space::capacity | @To Space::used | 해당 없음 |
| 뉴 세대 (Xmn과 다름) | eden + 하나의 survivor 공간 | 해당 영역 사용량 | 해당 없음 |
| 오래된 세대 (Old Gen) | @concurrent mark-sweep generation::capacity | 해당 영역 사용량 | 사용자 설정 |
| PermGen (JDK 8 이후는 Metaspace) | @Perm Generation::capacity | @Perm Generation::used | 사용자 설정 |
메모리 설정 파라미터 조회
| 설정 항목 | 조회 방법 |
|---|---|
| MaxTenuringThreshold | jinfo -flag MaxTenuringThreshold <PID> |
| MinHeapFreeRatio | jmap -heap <PID> → MinHeapFreeRatio |
| MaxHeapFreeRatio | jmap -heap <PID> → MaxHeapFreeRatio |
| 뉴 세대 GC 유형 | jmap -heap <PID> 출력에서 확인 |
| 오래된 세대 GC 유형 | 뉴 세대 정보 아래에 포함 |
| 클래스 총 수 | 아직 정확한 표준 명령어 없음 |
C Heap 사용량 확인
시스템 수준에서 메모리 사용량을 확인하려면 다음 명령어 사용:
top
ps aux
메모리 사용자를 식별하기 위한 방법
- Java 힙:
jmap -histo <PID>또는jmap -dump:format=b,file=heap.hprof <PID>후 MAT(Memory Analyzer Tool)로 분석 - C Heap: Google Perftools (tcmalloc 등) 활용
GC 활동 지표
| 행동 | 발생 횟수 | 처리 시간 (초) | 애플리케이션 정지 시간 |
|---|---|---|---|
| Full GC | #FGC | #FGCT | -XX:+PrintGCApplicationStoppedTime 옵션 활성화 시 로그에서 확인 |
| Youth GC | #YGC | #YGCT | 동일하게 로그에서 확인 가능 |
추가적인 GC 로깅을 활성화하려면 다음 옵션을 사용합니다:
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationStoppedTime -Xloggc:logs/gc.log
주요 내장 도구 소개
jinfo: 실행 중인 JVM 설정 조회 및 수정
실행 중인 프로세스의 시스템 속성 및 JVM 플래그를 확인하거나 변경할 수 있습니다. 단, 일부 축약된 플래그는 조회되지 않을 수 있으며, 이는 기본값을 확인하는 데 유용합니다.
지원 운영체제: Solaris, Linux
사용 예시:
jinfo -flag MaxTenuringThreshold <PID>
jmap: 힙 메모리 상태 분석
실행 중인 JVM의 물리적 메모리 사용 상황을 조사합니다. 힙 내 객체의 분포를 살펴보고, GC 전후 차이를 비교하기 위해 힙 덤프를 저장하는 것이 좋습니다.
예시 명령어:
jmap -heap <PID> jmap -dump:format=b,file=heap.hprof <PID>
덤프 파일은 Eclipse Memory Analyzer (MAT)로 분석 가능하며, 객체 수, 메모리 점유율, 스레드 상태 등을 확인할 수 있습니다.
주의: jmap -dump:live 명령은 전체 힙 덤프를 생성하지 않고 살아 있는 객체만 포함하므로, 내부적으로 Full GC를 트리거할 수 있습니다.
jstack: 스레드 상태 확인
실행 중인 애플리케이션의 모든 스레드 상태를 확인하거나, 크래시 발생 시 생성된 core 파일에서 스택 트레이스를 추출할 수 있습니다. 프로그램이 응답하지 않는 경우(허깅 상태)에도 매우 유용합니다.
지원 운영체제: Solaris, Linux
사용 예시:
jstack <PID>
jstat: JVM 실시간 통계 모니터링
JVM 내장 통계 기능을 활용하여 힙/비힙 크기, 클래스 로더, 컴파일러, GC 상태 등을 실시간으로 모니터링합니다.
사용 예시:
jstat -printcompilation -h10 3024 250 600
각 250밀리초마다 출력하고, 600번 반복하며 10줄마다 헤더 재출력.
옵션 설명
-h <n>: n줄마다 헤더 표시-t: 시작 시간부터 경과 시간을 첫 번째 컬럼에 표시-J<arg>: JVM 인스턴스 파라미터 수정 (예:-J-Xms48m)<vmid>: 대상 프로세스 ID<interval>: 간격 (밀리초)<count>: 반복 횟수
주요 옵션 및 출력 컬럼 설명
| 옵션 | 설명 |
|---|---|
| class | 클래스 로더 동작 통계 |
| compiler | JIT 컴파일러 성능 |
| gc | GC 관련 힙 상태 |
| gccapacity | 세대별 힙 용량 정보 |
| gccause | GC 원인 및 최근 발생 사유 |
| gcnew | 새로운 세대의 자세한 통계 |
| gcnewcapacity | 새로운 세대의 공간 크기 |
| gcold | 오래된 세대 통계 |
| gcoldcapacity | 오래된 세대 용량 정보 |
| gcpermcapacity | 퍼먼젠 용량 정보 |
| gcutil | GC 통계 요약 (백분율 기반) |
| printcompilation | 컴파일된 메서드 정보 |
Java API를 통한 프로그래밍 방식 모니터링
Java 5 이상부터 제공되는 java.lang.management 패키지를 통해 실행 중인 JVM의 다양한 정보를 코드로 접근할 수 있습니다. 이 패키지는 ManagementFactory를 통해 다양한 MBean 인스턴스를 가져올 수 있도록 지원합니다.
예제 1: 현재 로드된 클래스 수 확인
public class ClassLoaderChecker {
public static void main(String[] args) throws Exception {
ClassLoadingMXBean bean = ManagementFactory.getClassLoadingMXBean();
System.out.println("Loaded classes: " + bean.getLoadedClassCount());
}
}
예제 2: 사용자 정의 MBean 등록 및 모니터링
public interface DemoMBean {
AtomicLong getInvokeCount();
}
public class DemoImpl implements DemoMBean {
private final AtomicLong invokeCount = new AtomicLong(0);
public AtomicLong getInvokeCount() {
return invokeCount;
}
public static final String OBJECT_NAME = "com.redcreen.demo:type=demo";
public static void register() {
try {
ObjectName name = new ObjectName(OBJECT_NAME);
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(new DemoImpl(), name);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
등록 후, jconsole 또는 자체 코드로 해당 데이터를 실시간으로 조회 가능.