JVM(2)에서 G1GC의 동작에 대해 알아보았다. HotSpotVM에는 이러한 동작을 샘플링해서 조회할 수 있는 jstat이라는 내장 도구가 있다. HostSpotVM이 내부적으로 수집하는 GC 및 Heap 통계를 조회할 수 있다.
실험적인 기능이고 GC 유형과 세부 단계는 알 수 없지만, 별도 설치없이 간편하게 GC 및 Heap 영역 상태를 모니터링할 수 있기에 가벼운 모니터링에 사용되고있다.
조회를 위해서는 jcm를 통해 process id를 먼저 찾고, 그후 pid를 기준으로 모니터링을 시작하면 된다.
% jcmd -l
74208 jdk.jcmd/sun.tools.jcmd.JCmd -l
73529 com.example.gc_test.GcTestApplication
73484 com.intellij.idea.Main
73516 org.gradle.launcher.daemon.bootstrap.GradleDaemon 8.14.4
% jstat -gc <pid> 1s # 1s = 1초
명령어를 실행하면 다음과 같은 필드들이 출력되며, 각 항목의 의미는 다음과 같다.

-Young Generation
S0C, S1C : Survivor0, Survivor1 Capacity의 줄임말. S0, S1 영역의 전체 크기를 말한다.
S0U, S1U : Survivor0, Survivor1 Used Memory의 줄임말. S0, S1 영역의 사용량을 보여준다.
EC : Eden 영역의 전체 크기
EU : Eden 영역 중 사용된 크기
-Old Generation
OC : Old 영역의 전체 크기
OU : Old 영역 중 사용된 크기
-Metaspace
MC : Metaspace의 전체 크기
MU : Metaspace 영역 중 사용된 크기
CCSC : Compressed Class Space Capacity
CCSU : Compressed Class Space Used
-GC 통계
YGC : Young GC Count
YGCT : Young GC에 소요된 시간의 합
FGC : Full GC Count
FGCT : Full GC에 소요된 시간의 합
CGC : Concurrent GC Count
CGCT : Concurrent GC에 소요된 시간의 합
GCT : Garbage Collection Time. 전체 GC에 소요된 시간의 합
이제 실제 jstat 출력을 기반으로 GC 동작을 관찰해보자.
1. Young Generation 위주의 GC가 필요한 케이스

실험을 위해 Spring Boot로 간단한 API를 구성했다.
해당 API는 요청마다 길이 10,000의 ArrayList<Long>을 생성하며, 단일 API 요청이 끝나면 생성된 객체들은 Unreachable 상태가 되어 GC 대상이 된다.
먼저 눈에 띄는 점은 S0 영역의 Capacity와 Used 값이 모두 0으로 표시된다는 것이다.
이는 JDK 17에서 기본 GC로 사용되는 G1GC의 동작 특성 때문이다. G1GC는 Survivor 영역을 고정된 S0/S1 swap 방식으로 사용하지 않고, Region 단위로 evacuation을 일으켜서 사용한다. 관측 시점에 사용된 영역이 모두 S1 영역으로 인식된 것으로 보인다.
또한 YGC 값이 4 → 5로 증가하는 시점에서 Eden 영역의 사용량(EU)이 0으로 초기화된 것을 확인할 수 있다.
동시에 Young GC가 발생하면서 Eden 영역의 객체들이 Survivor로, Survivor 객체가 Old 영역으로 이동한 것도 확인할 수 있다.
2. Old Generation의 GC가 필요한 케이스

이번에는 Controller에 ConcurrentLinkedDeque<Long> 타입의 공유 변수를 두고,
POST /permanent 호출 시 해당 영역에 10,000개의 Long 객체를 추가하도록 구성했다.
이후 POST /clear 호출 시 큐를 비워서 객체를 Unreachable 상태로 만들고, /permanent → /clear 호출을 반복하면서 Old 영역의 변화를 유도했다.
첫번째 줄 이전에 /clear를 호출 후 다시 /permanent 호출이 이루어졌고, 몇번의 Young GC 이후 Old generation의 사용량이 증가하며 세번째 줄에서 Mixed GC가 발생한 상황이다. 필드별로 자세히 알아보자.
두번째 줄은 Young GC만 발생한 상황이다.
GC에 의해 EU 영역의 사용량은 줄었지만, Eden의 객체가 Survivor 영역으로, Survivor 객체가 Old 영역으로 승격되면서 두 영역의 사용량은 증가한다.
이어 세번째 줄에서 Mixed GC가 발생했다.
/clear API 호출로 인해 상당수의 객체가 Unreachable 상태였으므로, S1U, EU, OU 세 영역의 사용량이 모두 감소했다.
일반적인 Young GC에서는 Old 영역 사용량이 증가하거나 유지되는 반면, 이처럼 Old 영역이 함께 감소하는 경우는 G1GC에서의 Mixed GC와 유사한 패턴이다.
또한 동일 구간에서 CGC 값이 증가한 점을 고려하면, Concurrent Mark 이후 일부 Old Region이 함께 회수되었을 가능성이 높다.
물론 정확한 GC 유형은 -Xlog:gc* 로그를 통해 확인하는 것이 가장 확실하다.
다만 jstat 지표 변화와 실험 시나리오를 기반으로 G1GC의 동작 특성에 추론해본 것에 가깝다.
또한 실제 실무에서는 jstat을 사용하기보다는 Spring Actuator를 통해 필요한 JVM 지표를 수집한다고 하니, jstat으로 GC 동작을 관찰해본 데에 의의를 두자.
참고한 자료
'프로그래밍 언어 > Java' 카테고리의 다른 글
| JVM(2) - G1GC의 동작과정 (0) | 2026.04.11 |
|---|---|
| JVM(1) - JVM 작동 과정 (0) | 2026.04.05 |