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 와 함께 커질 수 있습니다.
Read Next
- GC overhead 가 spike 를 밀어 올리는 것 같다면 Java GC Pauses Too Long 을 이어서 보세요.
- 서비스가 단순히 뜨거운 수준이 아니라 멈추는 쪽에 가깝다면 Java Thread Deadlock 과 비교해 보세요.
- hot stack 에 contention 이 보이면 Java Thread Contention High 도 같이 보세요.
- queued work 도 함께 쌓이면 Java Thread Pool Queue Keeps Growing 와 연결해서 보는 편이 좋습니다.
- 전체 Java 증상 지도로 돌아가려면 Java Troubleshooting Guide 를 보면 됩니다.
Related Posts
- Java GC Pauses Too Long
- Java Thread Deadlock
- Java Thread Contention High
- Java Thread Pool Queue Keeps Growing
- Java Troubleshooting Guide
Sources:
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis vs RabbitMQ vs Kafka 개발자를 위한 미들웨어 트러블슈팅 허브 글입니다. Redis, RabbitMQ, Kafka 중 어떤 증상부터 먼저 봐야 하는지와 어떤 문제 패턴이 각 시스템에 가까운지 정리합니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Kafka consumer lag가 계속 늘 때: 트러블슈팅 가이드 Kafka consumer lag가 계속 늘어날 때 무엇부터 봐야 하는지 정리합니다. poll 주기, 처리 속도, rebalance, consumer 설정까지 실전 기준으로 다룹니다.
- Kafka Rebalancing Too Often 가이드 Kafka consumer group에서 rebalance가 너무 자주 일어날 때 membership flapping, poll timing, protocol, assignment churn을 어떤 순서로 봐야 하는지 설명하는 실전 가이드입니다.
- Docker container가 계속 재시작될 때: 먼저 확인할 것들 exit code, command failure, environment mistake, health check 관점에서 Docker restart loop를 푸는 실전 가이드입니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.