JDK 25에서 JFR을 활용한 편향 없는 Java CPU 프로파일링 (새 탭에서 열림)
Java Flight Recorder(JFR)는 저부하·상시 운영에 적합한 강력한 진단 도구지만, CPU 사용량을 정확히 반영해야 하는 프로파일링에서는 한계가 있다. 특히 ExecutionSample은 실제 CPU 시간에 비례해 샘플링하지 않아 CPU 집약적이거나 리액티브한 애플리케이션의 핫스팟을 과소·과대평가할 수 있다. 따라서 현대 프로파일러는 JFR의 안정성과 AsyncGetCallTrace·JVMTI 기반 샘플링의 정확성을 결합해 사용하며, 장기적으로는 JVM이 공식 지원하는 CPU 프로파일링 이벤트가 필요하다는 것이 글의 결론이다.
연속 프로파일링의 기본 원리
- 프로파일러는 일정 시간 동안 스택 트레이스를 반복 수집하고 집계해 애플리케이션의 동작 패턴을 파악한다.
- 샘플링 시점은 프로파일러마다 다르다.
- CPU 시간 기반: 실제로 스레드가 CPU를 사용하는 동안만 시간이 증가한다.
- 벽시계 시간 기반: 실행, 대기, I/O, 락 대기 등 모든 경과 시간을 포함한다.
- CPU 시간 프로파일링은 연산 핫스팟을 찾는 데 적합하다.
- 예: 무한 루프나 계산 집중 메서드
- 벽시계 시간 프로파일링은 지연 원인을 찾는 데 유용하다.
- 예: 느린 데이터베이스 쿼리, 락 대기, I/O
- 메모리 할당, 락 경합, 스레드 파킹, GC 등 특정 이벤트를 기준으로 스택을 수집하는 방식도 있다.
- 어떤 이벤트를 사용하든 핵심은 “이벤트가 발생했을 때 어떤 코드가 실행 중이었는가”를 스택으로 기록하고 누적하는 것이다.
JFR ExecutionSample의 장점과 한계
- JFR은 JVM에 내장되어 있으며 GC, 클래스 로딩, 스레드 스케줄링, 메모리 할당 등 다양한 런타임 이벤트를 구조화해 제공한다.
- 낮은 오버헤드로 상시 실행할 수 있어 운영 환경에 적합하다.
- CPU 프로파일링에는
ExecutionSample이벤트가 사용된다. - 그러나
ExecutionSample은 운영체제 수준에서 실제 CPU 시간을 직접 기준으로 샘플링하지 않는다.- JVM이 관찰한 실행 가능 스레드 중 일부를 순환하며 샘플링한다.
- CPU를 많이 사용하는 스레드가 더 자주 나타날 수는 있지만, 실제 CPU 사용량에 정확히 비례하지는 않는다.
- CPU가 포화된 환경이나 리액티브 애플리케이션에서는 스레드 스케줄링 특성 때문에 특정 CPU 핫스팟이 충분히 나타나지 않을 수 있다.
- 결과적으로 프로파일이 틀렸다기보다는 데이터가 불완전해져 원인 분석 시간이 길어질 수 있다.
AsyncGetCallTrace를 이용한 CPU 샘플링
- JVMTI 에이전트와 운영체제 신호인
SIGPROF를 사용하면 CPU 시간에 기반해 샘플링할 수 있다. - 신호가 발생한 스레드 안에서 HotSpot의
AsyncGetCallTrace를 호출해 비동기적으로 Java 스택을 추적한다. - 이 방식은 세이프포인트 편향을 피하고 JFR에 정의된 이벤트가 아닌 임의의 CPU 이벤트에서도 스택을 수집할 수 있다.
async-profiler같은 도구가 이 접근법을 널리 확산시켰다.- 실제 CPU 사용량에 가까운 결과를 제공하므로 CPU-bound 작업 분석에 특히 효과적이다.
내부 JVM API 의존성 문제
AsyncGetCallTrace는 공식 공개 API가 아니라 HotSpot 내부 메커니즘이다.- OpenJ9, Zing 등 다른 JVM에서도 지원되지만 안정적인 표준 인터페이스로 설계된 것은 아니다.
- 높은 부하나 특수한 상황에서는 오류를 일으킬 가능성이 있어 프로파일러가 JVM 장애를 방지하기 위한 방어 로직을 추가해야 한다.
- Datadog 프로파일러는 다음 방식을 조합한다.
- JFR: 안정적인 런타임 텔레메트리 수집
AsyncGetCallTrace: 정확한 Java CPU 스택 샘플링vmstructs워킹: JVM 내부 메타데이터를 직접 읽어 스택과 런타임 상태 복원
vmstructs는 표준 API가 노출하지 않는 정보를 얻을 수 있지만, 역시 JVM 내부 구조에 의존한다.- 따라서 프로파일러 제작자는 다음과 같은 절충을 해야 한다.
- JFR만 사용하면 안전하지만 CPU 샘플링 정확도가 떨어질 수 있다.
- 내부 API를 사용하면 정확하지만 JVM 안정성과 유지보수 부담이 커진다.
- 실제 상용 프로파일러들은 대체로 두 방식을 함께 사용한다.
JVM 프로파일링 기반을 개선하려는 움직임
- Datadog, SAP, Amazon, OpenJDK 커뮤니티는 이 문제가 특정 회사만의 문제가 아니라는 데 공감했다.
- JFR은 이미 운영 환경용 프로파일링 기반으로 적합하지만, 정확한 CPU 샘플링을 위한 공식 이벤트가 부족했다.
- 기존 생태계가 비공개·비공식 HotSpot API에 의존하는 것은 장기적으로 바람직하지 않다.
- 글에서는 이러한 한계를 해결하기 위해 JVM에 안전성과 정확성을 모두 갖춘 “일급 CPU 프로파일링 이벤트”를 추가하려는 협력 과정을 소개한다.
- 제공된 원문은 OpenJDK 논의와 새 이벤트의 구체적인 설계 설명이 시작되는 부분에서 중단되어 있다.
운영 환경에서는 JFR만으로 CPU 병목을 단정하기보다, CPU 시간 기반 샘플링을 지원하는 프로파일러를 함께 사용하는 것이 좋다. 다만 JVM 내부 API 의존성은 안정성 위험을 동반하므로, 장기적으로는 공식 JFR CPU 프로파일링 이벤트가 제공되는 JVM과 프로파일러를 선택하는 것이 바람직하다.