[JAVA] CPU 100% 찍는 Java 프로세스 원인 찾는 방법

운영 중인 Java 프로세스가 갑자기 CPU 100%를 찍는 상황은 한 번쯤 겪게 됩니다. 단순히 "부하가 많다"로 끝내면 해결이 되지 않습니다. 어떤 스레드가, 어떤 코드에서 CPU를 쓰고 있는지를 정확히 짚어내야 합니다.

Java CPU 100% 원인 파악의 기본 접근

Java에서 CPU가 100%까지 치솟는 경우는 대부분 특정 스레드가 계속해서 일을 하고 있는 상황입니다. I/O 대기 상태가 아니라 CPU를 계속 점유하고 있다는 의미입니다.

따라서 접근 방식은 단순합니다. "어떤 스레드가 CPU를 많이 쓰는지" → "그 스레드가 어떤 코드를 실행 중인지" 이 흐름으로 추적합니다.

1. OS 레벨에서 문제 스레드 찾기

가장 먼저 해야 할 일은 CPU를 많이 쓰는 스레드를 식별하는 것입니다. 이 단계에서 Java 도구를 바로 쓰지 않고, OS 도구를 먼저 사용하는 이유는 전체 프로세스 중 어디서 문제가 발생했는지 빠르게 좁히기 위해서입니다.


top -Hp [PID]

이 명령어를 실행하면 해당 Java 프로세스 내부의 스레드 단위로 CPU 사용량을 확인할 수 있습니다. 여기서 CPU를 많이 사용하는 TID(Thread ID)를 확인합니다.

실무에서는 이 단계에서 이미 절반은 해결된 상태입니다. 문제 스레드만 정확히 잡아도 분석 범위가 크게 줄어듭니다.

2. 스레드 ID를 Java Thread Dump와 매칭

OS에서 확인한 TID는 10진수입니다. Java Thread Dump에서는 16진수로 표현됩니다. 따라서 변환 과정이 필요합니다.


printf "%x\n" [TID]

이렇게 변환한 값을 Thread Dump에서 nid 값으로 검색하면 해당 스레드를 찾을 수 있습니다.


jstack [PID] > threaddump.txt

여기서 중요한 포인트는 "RUNNABLE 상태"인 스레드를 찾는 것입니다. CPU를 실제로 사용하는 스레드는 대부분 RUNNABLE 상태입니다.

 

CPU 100%를 유발하는 대표적인 원인

Thread Dump를 보면 대부분 패턴이 보입니다. CPU를 많이 쓰는 경우는 몇 가지 유형으로 수렴하는 편입니다.

무한 루프 (while true)

가장 흔한 케이스입니다. 종료 조건이 잘못되었거나, break가 빠진 경우입니다.


while (true) {
    // 종료 조건 없음
}

단순해 보이지만, 조건식이 잘못되어 사실상 무한 루프가 되는 경우도 많습니다. 특히 flag 값을 다른 스레드에서 변경하는 구조라면 더 헷갈리기 쉽습니다.

busy-waiting 구조

sleep 없이 계속 상태를 체크하는 코드입니다. 기능적으로는 문제가 없어 보여도 CPU를 계속 점유합니다.


while (!flag) {
    // 아무것도 안하고 계속 체크
}

이 경우에는 sleep을 추가하거나, wait/notify 구조로 변경하는 것이 일반적인 해결 방법입니다.

잘못된 컬렉션 반복

데이터가 예상보다 커졌을 때 CPU를 과도하게 사용하는 경우입니다.

코드 자체는 문제가 없어 보여도, 반복 횟수가 급격히 증가하면 CPU를 계속 사용하게 됩니다. 이 부분은 데이터 크기와 함께 봐야 정확히 판단할 수 있습니다.

락 경합 없이 계속 실행되는 스레드

동기화가 잘못된 경우, 특정 스레드가 계속 실행되고 다른 스레드는 대기하는 구조가 됩니다. 이 경우 특정 스레드만 CPU를 계속 사용하게 됩니다.

 

실무에서 빠르게 원인 좁히는 방법

이론적으로는 Thread Dump를 한 번 보면 되지만, 실제로는 한 번으로는 부족한 경우가 많습니다.

Thread Dump를 여러 번 찍는다

짧은 간격으로 2~3번 Thread Dump를 찍어보면 동일한 위치에서 계속 실행 중인 스레드를 찾기 쉽습니다.

한 번만 보면 우연히 해당 코드에 있었던 것인지 판단하기 어렵습니다. 반복해서 동일한 스택에 걸려 있다면 실제 원인일 가능성이 높습니다.

코드 위치를 기준으로 본다

Thread Dump를 보면 메서드 호출 스택이 쭉 나옵니다. 이때 상위 레벨보다 실제 반복이 일어나는 지점을 보는 것이 중요합니다.

예를 들어 Controller가 아니라 내부 loop나 util 코드에서 계속 실행되고 있는 경우가 많습니다.

 

자주 놓치는 포인트

CPU 문제를 볼 때 몇 가지 자주 놓치는 부분이 있습니다.

GC 문제로 오해하는 경우

CPU가 높다고 해서 항상 GC 문제는 아닙니다. GC는 별도의 로그나 상태로 확인해야 합니다. Thread Dump에서 애플리케이션 코드가 계속 실행 중이라면 GC가 아니라 코드 문제입니다.

I/O 대기와 CPU 사용을 혼동

WAITING 상태와 RUNNABLE 상태는 의미가 다릅니다. CPU를 쓰는 것은 RUNNABLE 상태이고 이 구분을 정확히 해야 합니다.

 

정리

Java CPU 100% 문제는 복잡해 보이지만 접근 방식은 단순합니다.

CPU를 많이 쓰는 스레드를 찾고 → 해당 스레드의 스택을 확인하고 → 반복되는 코드 위치를 찾는 흐름입니다.

이 과정을 한 번 익혀두면 대부분의 CPU 문제는 같은 방식으로 해결할 수 있습니다. 결국 중요한 것은 도구가 아니라, 어디를 봐야 하는지 기준을 잡는 것입니다.