Java JVM CPU usage가 높을 때 먼저 볼 것들
마지막 업데이트

Java JVM CPU usage가 높을 때 먼저 볼 것들


Java 에서 CPU usage 가 높아질 때 가장 흔한 실수는 이걸 하나의 일반적인 확장 문제로 보는 것입니다. 실제로 high CPU 는 서로 전혀 다른 이유에서 생길 수 있습니다. 진짜 애플리케이션 작업이 많아졌을 수도 있고, GC 부담이 CPU 로 번졌을 수도 있고, retry loop, lock contention, spin loop 처럼 쓸모 없는 작업이 CPU 를 태우고 있을 수도 있습니다.

짧게 말하면 핵심은 이것입니다. 먼저 hot thread 를 잡고, 같은 시점의 GC activity 와 함께 비교해야 합니다. 호스트 CPU 그래프는 압력이 있다는 사실만 알려 줍니다. 어떤 thread stack 이 CPU 를 쓰는지 봐야 그게 유용한 작업인지, 메모리 정리인지, retry 인지, contention 인지 구분할 수 있습니다.

Java 전체 증상 분기부터 다시 보고 싶다면 Java Troubleshooting Guide 로 먼저 돌아가도 좋습니다.


host CPU 그래프보다 hot thread 부터 본다

머신 단위 CPU 그래프는 도움이 되지만, 어디에서 CPU 가 타는지는 말해 주지 않습니다.

처음에 나눠야 할 구분은 이렇습니다.

  • 애플리케이션 코드가 쓰는 CPU
  • garbage collection 이 쓰는 CPU
  • retry, spin, coordination 처럼 낭비되는 CPU

이 구분이 중요한 이유는, 원인마다 fix 방향이 완전히 달라지기 때문입니다.


high CPU 는 운영에서 어떻게 보이나

보통 이런 증상과 함께 나타납니다.

  • request latency spike
  • queue growth 또는 backlog
  • 높은 allocation rate
  • thread contention 또는 blocked worker 부작용
  • burst traffic 이후 CPU 가 잘 내려오지 않음

어떤 경우에는 정말 서비스가 바빠서 CPU 가 높은 것일 수 있습니다. 하지만 꽤 자주, high CPU 는 wasted work 나 다른 병목의 부산물일 때도 많습니다.


흔한 원인

1. busy request path 또는 tight loop

아래 같은 변화 이후 애플리케이션 코드가 예상보다 훨씬 많은 일을 할 수 있습니다.

  • traffic 증가
  • payload size 변화
  • 새 기능 배포
  • 우발적인 quadratic behavior

hot stack 이 parsing, serialization, filtering, sorting, repeated transformation 쪽을 가리킨다면, CPU 증가는 실제 유용한 작업량 증가일 수 있습니다.

2. GC overhead 가 너무 커졌다

allocation churn 이 memory pressure 를 CPU pressure 로 바꿔 놓을 수 있습니다.

이런 경우가 그렇습니다.

  • 큰 temporary object 를 빠르게 많이 만든다
  • fan-out 이 short-lived allocation 을 늘린다
  • cache 나 queue 가 예상보다 많이 잡고 있다
  • heap pressure 때문에 collection 빈도가 높아진다

이때는 겉으로 CPU-bound 처럼 보여도, 더 깊은 원인은 메모리 동작일 수 있습니다.

3. lock contention, retry, spin loop 가 CPU 를 낭비한다

모든 CPU 사용이 생산적인 것은 아닙니다.

thread 가 반복해서:

  • 깨어나고 retry 하거나
  • shared state 를 polling 하거나
  • 같은 lock 을 두고 경쟁하거나
  • availability check 를 spin 하거나

이런 식이면 throughput 증가 없이 CPU 만 높아질 수 있습니다.

4. backlog pressure 가 CPU 를 다른 층으로 밀어 올린다

queue 가 쌓이고, worker 가 포화되고, connection wait 가 번지면 시스템은 아래 같은 데 CPU 를 더 쓰게 됩니다.

  • scheduling
  • retry
  • timeout handling
  • queue management

그러면 보이는 CPU spike 가 실제 병목을 가리게 됩니다.

5. thread 가 너무 많아 shared state 를 두고 싸운다

thread 수를 늘리는 것이 오히려 CPU 를 악화시키는 경우도 있습니다.

추가된 thread 는 아래를 늘릴 수 있습니다.

  • context switching
  • monitor contention
  • failed acquisition attempt
  • queued 또는 duplicated work 에서 오는 GC pressure

그래서 thread count 는 보통 첫 번째 조정 포인트가 아닙니다.


실전 점검 순서

1. incident 시점의 hot thread 를 잡는다

실제로 CPU 를 태우는 thread 부터 봐야 합니다.

쓸 만한 명령은 이렇습니다.

top -H -p <pid>
jcmd <pid> Thread.print

뜨거운 OS thread 를 Java stack 과 매칭하는 것이, “CPU 가 높다” 에서 실제 culprit 으로 가장 빨리 이동하는 방법인 경우가 많습니다.

2. CPU spike 와 GC activity 를 비교한다

아래를 같이 봅니다.

  • GC frequency
  • GC pause timing
  • allocation rate
  • old generation pressure

CPU spike 가 GC churn 과 같은 시점에 맞물린다면, heap 동작이 원인 일부일 가능성이 높습니다.

3. retry, polling, contention 을 찾는다

질문은 이렇습니다.

  • 계속 상태를 확인하는 loop 가 있는가
  • timeout 이 retry storm 으로 번졌는가
  • 많은 thread 가 하나의 monitor 를 두고 싸우는가

throughput 이 CPU 만큼 오르지 않는다면 wasted work 가능성을 크게 봐야 합니다.

4. queue growth 와 latency 를 CPU rise 와 같이 본다

queue depth 와 latency 가 CPU 보다 먼저 오르기 시작했다면, high CPU 는 원인이라기보다 overload 의 결과일 수 있습니다.

특히 아래 상황에서 흔합니다.

  • executor saturation
  • aggressive retry
  • downstream slowdown

5. source 가 분명해진 뒤에만 thread 나 heap 을 튜닝한다

문제가 진짜 작업량 증가라면 scaling 이 도움이 될 수 있습니다.

문제가 wasted work 나 memory churn 이라면 scaling 만으로는 비싼 장애가 될 뿐입니다.


예시: “CPU 높음” 으로 보이는 hot loop

while (!ready.get()) {
    // keep checking
}

작은 테스트에서는 harmless 해 보여도, 운영 부하에서는 유용한 일을 하지 않으면서 core 를 계속 태울 수 있습니다.

보통 더 나은 방향은 이렇습니다.

  • proper signal 로 기다리기
  • spin 대신 backoff 넣기
  • needless polling 줄이기

패턴을 찾은 뒤 무엇을 바꾸면 좋나

hot stack 이 real work 를 가리킨다면

비싼 경로를 최적화하거나 의도적으로 scale 해야 합니다.

hot stack 이 GC 를 가리킨다면

random heap flag 를 바꾸기보다 allocation churn 과 retention 을 먼저 봐야 합니다.

hot stack 이 retry 나 spin 을 가리킨다면

waste 를 먼저 줄여야 합니다. backoff, deduplication, coordination path 재설계가 우선입니다.

hot stack 이 contention 을 가리킨다면

critical section 을 줄이고 shared-state 설계를 다시 보는 편이 thread 추가보다 낫습니다.

CPU 가 queue growth 뒤에 올라간다면

CPU 보다 queue 와 backpressure 문제를 먼저 다뤄야 할 가능성이 큽니다.


장애 중에 던져볼 질문

이 질문이 꽤 유용합니다.

지금 CPU 는 유용한 애플리케이션 작업에 쓰이고 있나, 메모리 정리에 쓰이고 있나, 아니면 없어도 될 작업에 낭비되고 있나?

이 질문이 단순히 “왜 CPU 가 높지?” 보다 훨씬 행동 가능한 답을 줍니다.


FAQ

Q. CPU 가 높으면 thread 가 부족한 건가

아닙니다. thread 를 늘리면 contention, scheduling overhead, GC pressure 가 더 나빠질 수 있습니다.

Q. 가장 빠른 첫 단계는 무엇인가

hot thread 를 잡고 같은 incident window 의 GC activity 와 비교하는 것입니다.

Q. 먼저 scale out 해야 하나

CPU rise 가 real work 때문인지, wasted work 때문인지, memory pressure 때문인지 구분한 뒤가 좋습니다.

Q. queue backlog 도 CPU spike 를 만들 수 있나

그렇습니다. retry, scheduling churn, coordination overhead 가 backlog 와 함께 커질 수 있습니다.


Sources:

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.