Java GC pause가 너무 길 때 먼저 볼 것들
마지막 업데이트

Java GC pause가 너무 길 때 먼저 볼 것들


Java 에서 GC pause 가 길어질 때 가장 흔한 실수는 이걸 일반적인 JVM tuning 문제로만 보는 것입니다. 긴 pause 는 보통 allocation pressure, retention, 혹은 현재 workload 와 맞지 않는 heap 구조를 반영합니다. collector flag 를 너무 빨리 바꾸면 실제 원인을 해결하지 못한 채 잠깐만 가려 버릴 때가 많습니다.

짧게 말하면 핵심은 이것입니다. pause shape, allocation rate, old generation growth 를 같이 봐야 합니다. 아주 긴 stop-the-world pause 가 가끔 오는 경우와, 짧지만 잦은 pause 가 계속 오는 경우는 같은 병목을 뜻하지 않습니다.

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


평균 latency 보다 pause 모양부터 본다

평균 latency 하나로는 실제 GC pattern 이 가려질 수 있습니다.

구분해야 할 것은 이렇습니다.

  • 드물지만 매우 큰 pause
  • 자주 반복되는 중간 크기 pause
  • traffic burst 때만 커지는 pause
  • old generation pressure 와 맞물리는 pause

이 모양이 churn, retention, sizing, workload shift 중 어느 쪽인지 방향을 잡아 줍니다.


긴 GC pause 는 운영에서 어떻게 보이나

보통 이런 증상과 같이 보입니다.

  • request latency spike
  • collection 구간의 CPU 상승
  • GC 후에도 old generation 이 높게 남아 있음
  • traffic burst 이후 recovery 가 느림
  • 겉보기엔 충분해 보이는 heap 이 실제로는 pause 를 길게 만든다

즉 visible pause 는 investigation 을 시작하게 만드는 마지막 증상이지, 첫 번째 이상 신호가 아닌 경우가 많습니다.


흔한 원인

1. allocation churn 이 너무 크다

짧게 살 객체가 너무 많으면 collection 빈도가 높아지고 load 상황에서 pause spike 가 더 잘 보입니다.

특히 이런 경로가 그렇습니다.

  • 높은 request fan-out
  • 반복적인 object transformation
  • 큰 temporary buffer
  • serialization-heavy path

allocation rate 가 크게 오르면 빨리 죽는 객체도 pause 비용을 크게 만들 수 있습니다.

2. old generation retention 이 커진다

큰 구조가 너무 오래 살아 있으면 major collection 이 느려지고 회복도 어려워집니다.

대표적인 source 는:

  • cache
  • in-memory queue
  • response aggregation
  • long-lived collection

이건 단순 churn 이 아니라 retention 이 개입됐다는 강한 신호입니다.

3. heap sizing 이 현재 traffic 과 맞지 않는다

낮은 traffic 에서는 괜찮던 heap 도 아래 변화가 오면 pause 가 길어질 수 있습니다.

  • request volume 증가
  • 더 큰 payload
  • concurrency 증가
  • object lifetime 변화

그렇다고 바로 “heap 을 늘리자” 는 뜻은 아닙니다. 예전 sizing assumption 이 현재 운영 현실과 안 맞는다는 뜻에 가깝습니다.

4. 진짜 병목은 GC 바깥에 있다

CPU saturation, blocked thread, queue buildup, downstream slowdown 이 먼저 있고, GC 는 그 압력을 더 크게 보이게 하는 경우도 많습니다.

서비스가 이미 뒤처지고 있다면 GC 는 그 압력이 눈에 띄는 장소가 될 뿐입니다.

5. 큰 retained object 가 pause 를 비싸게 만든다

OOM 전이라도 oversized retained graph 가 있으면 pause time 이 충분히 길어질 수 있습니다.

그래서 GC incident 는 heap dump 분석과 바로 이어지는 경우가 많습니다.


실전 점검 순서

1. GC log 또는 pause metric 에서 frequency 와 worst-case spike 를 본다

평균 숫자 하나만 보면 안 됩니다.

질문은 이렇습니다.

  • pause 가 얼마나 자주 일어나는가
  • 최악의 pause 는 어느 정도인가
  • 최근 pattern 이 바뀌었는가

2. allocation rate 와 traffic 변화를 비교한다

배포나 workload 변화 뒤 allocation rate 가 크게 올랐다면, churn 이 지배적인 원인일 수 있습니다.

이 단계가 collector behavior 와 application behavior 를 나눠 줍니다.

3. old generation growth 와 long-lived retention 을 본다

GC 후에도 old generation 이 계속 높다면, collector tuning 보다 retention 에 더 집중해야 합니다.

4. 현재 workload 와 heap sizing 을 비교한다

heap 은 예전 traffic profile 에는 맞았을 수 있습니다.

그래도 memory 가 생산적으로 쓰이는지 모른 채 바로 heap enlargement 로 가는 건 피하는 편이 좋습니다.

5. retention 이 의심되면 heap analysis 로 넘어간다

pause 가 retained structure 와 맞물린다면, 다음으로 유용한 artifact 는 random JVM flag 조정이 아니라 heap dump 인 경우가 많습니다.


예시: retained object 때문에 늘어나는 pause

Map<String, byte[]> cache = new HashMap<>();
cache.put(key, new byte[10_000_000]);

큰 retained object 나 반복적인 대형 allocation 은 heap 이 아직 완전히 차지 않았더라도 GC pause 를 충분히 길게 만들 수 있습니다.

즉 긴 pause 는 full OOM 이 와야만 심각해지는 것이 아닙니다.


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

churn 이 핵심이라면

hot path 의 needless allocation 과 object copying 을 줄여야 합니다.

retention 이 핵심이라면

오래 살아 있는 graph 를 추적하고, 너무 오래 남는 구조를 줄여야 합니다.

heap sizing 이 낡은 가정이라면

workload 와 object lifetime story 를 이해한 뒤 의도적으로 resize 해야 합니다.

queue 나 backlog 가 더 깊은 문제라면

GC 만 탓하지 말고 throughput collapse 를 먼저 고쳐야 합니다.

CPU spike 도 함께 오른다면

runtime pressure 와 memory pressure 를 하나의 incident 로 봐야 합니다.


장애 중에 던져볼 질문

이 질문이 꽤 유용합니다.

pause 가 긴 이유가 GC 를 너무 자주 해서인가, old data 가 너무 많이 남아서인가, 아니면 workload 가 heap 설계를 넘어섰기 때문인가?

이 질문이 “collector 를 바꿔야 하나?” 보다 훨씬 행동 가능한 답을 줍니다.


FAQ

Q. collector 부터 바꿔야 하나

allocation churn, retention, undersizing 중 무엇이 중심 문제인지 보기 전에는 권하지 않습니다.

Q. pause 가 길면 memory leak 인가

항상은 아닙니다. bursty allocation, 큰 heap, workload 변화 때문일 수도 있습니다.

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

pause shape, allocation rate, old generation growth 를 같이 보는 것입니다.

Q. heap dump 는 언제 떠야 하나

pause behavior 와 old generation growth 를 본 뒤 retained heap 이 계속 수상할 때가 좋습니다.


  • pause spike 가 allocation churn 보다 retained heap 쪽으로 보이면 Java Heap Dump 를 이어서 보세요.
  • 같은 memory pressure 가 서비스 failure 로 가고 있다면 Java OutOfMemoryError 와 비교해 보세요.
  • GC pressure 와 함께 CPU 도 오른다면 Java JVM CPU High 를 같이 보세요.
  • 전체 Java 분기 지도를 다시 보려면 Java Troubleshooting Guide 로 돌아가면 됩니다.

Sources:

먼저 읽어볼 가이드

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