이 글에서 다루는 내용

JVM 상태 진단, systemd 서비스 옵션 튜닝까지의 트러블슈팅 과정을 정리한다.

JVM GC 상태 진단

jstat -gcutil 918054 2000 5
jstat -gcutil 2198075 2000 5
항목 blog-server word-mov
Metaspace 99.48% 99.41%
Old Gen 76.48% 84.59%
CGC 횟수 190 1040

두 서버 모두 Metaspace가 99% 가까이 꽉 차있었고, word-mov의 경우 CGC(Concurrent GC)가 1040번 발생해 blog 서버의 5배에 달했다.

왜 Metaspace가 꽉 차면 발열이 생기나

CPU 점유율이 낮아 보여도 JVM은 백그라운드에서 지속적으로 GC를 수행한다. Metaspace가 한계에 가까워지면 다음과 같은 루프가 반복된다.

Metaspace 99% 도달
    → JVM "공간 부족!"
    → Concurrent GC 트리거
    → 조금 비워짐
    → 다시 차오름
    → 또 CGC 트리거
    → 반복...

이 과정에서 CPU에 지속적인 소량의 부하가 걸리고, 그게 온도를 끌어올리고, 팬을 계속 돌게 만든다. top으로는 순간 점유율이 낮게 보여도 실제로는 쉬지 않고 일하고 있는 것이다.

해결 — systemd 서비스에 JVM 옵션 추가

sudo nano /etc/systemd/system/word-mov.service

기존 ExecStart:

ExecStart=/usr/bin/java -jar /srv/ss/app/word-mov.jar --spring.profiles.active=prod

수정 후:

ExecStart=/usr/bin/java \
  -Xms128m \
  -Xmx512m \
  -XX:MetaspaceSize=128m \
  -XX:MaxMetaspaceSize=256m \
  -XX:+UseG1GC \
  -XX:G1HeapRegionSize=4m \
  -jar /srv/ss/app/word-mov.jar --spring.profiles.active=prod

각 옵션의 역할:

옵션 역할
-Xms128m JVM 시작 힙 크기 128MB로 고정
-Xmx512m 힙 최대 512MB로 제한
-XX:MetaspaceSize=128m Metaspace 초기값 명시
-XX:MaxMetaspaceSize=256m Metaspace 무한 증가 방지
-XX:+UseG1GC G1GC로 교체하여 GC 효율 개선
-XX:G1HeapRegionSize=4m ARM 환경에 맞는 리전 크기 설정

적용:

sudo systemctl daemon-reload
sudo systemctl restart word-mov

예상 효과

이전 이후
word-mov 메모리 ~900MB ~512MB
blog-server 메모리 ~617MB ~256MB
전체 JVM 메모리 ~1,516MB ~768MB
CGC 빈도 1040회+ 대폭 감소 예상

메모리 자체를 줄이는 것보다 Metaspace에 명확한 상한선을 설정하는 것이 핵심이다. 제한이 없으면 JVM은 Metaspace를 계속 늘리다가 99%가 될 때마다 GC 폭탄을 터뜨린다.