[JAVA] GC, G1GC

2025. 6. 24. 21:24·java

그동안 얕게 알고 쓰던 자바를 정리하기 위한 포스팅 / 개인 기록용

 

자바를 사용한다면 Garbage Collector 일명 GC라는 것을 듣게 된다.

 

원래 C언어는 메모리를 동적으로 할당하고 나면, 원래 직접 개발자가 정리를 해주어야한다.

그러나 자바에서는 아무리 객체를 새로 생성해도 개발자가 직접 객체를 정리하지 않는다. 왜냐하면 GC가 알아서 해주기 때문이다.

 

JAVA 환경에서 JVM의 Heap 영역은 Young, Old 영역으로 구분되어 있으며 새로 생성되는 객체는 Young 영역에 저장되게 되며

Young 영역이 꽉 차게 되면 Young 영역을 한 번 쓱 훑어서 사용되지 않는 객체는 제거하고 오래된 객체는 Old 영역에 보낸다고 정도만

알고 있었다.

 

정확히는 GC 알고리즘마다 좀 다르게 동작하기에, 요즘 JDK 버전에서 일반적으로 흔히 사용되는 G1GC에 대해 정리한다.

JVM Heap & GC

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

GC의 목적은 사용되지 않는 객체(사용되지 않는 메모리)를 정리하는 것이다.

 

위 사진은 JVM Heap 영역의 논리적인 구조를 나타낸다. (물론 JDK 버전마다 각 위치의 용어도 다르고 구조도 약간 다르다.)

핵심은 Young과 Old 영역으로 구분되어 있고 Young은 내부적으로 eden과 2개의 survivor이 있다는 것이다.

 

JVM GC의 전제 조건 중 하나는 '최근에 생성된 객체는 금방 필요없어지게 된다'이다.

따라서 새로 생성되는 객체는 모두 eden에 저장되며 GC가 수행될 때 쉽게 처리할 수 있게 된다.

 

예를 들어 Young 영역에 공간이 꽉 차게 되는 경우 eden 영역을 먼저 스캔한 뒤 필요없는 애들을 전부 싹 날리고 살아남은 애들을 survivor로 넘기기만 하면 된다.

 

그리고 Young 영역을 청소하다보면 survivor에서 오래 살아남은 애들이 있을 것이다.

오래 살아남았다는 것은 특정 횟수이상 GC를 통과하는 경우를 의미하는 것인데 이런 객체들은 Old 영역으로 보내진다.

 

이처럼 단순히 Young 영역을 청소하고 오래된 객체를 Old 영역으로 보내는 GC를 대부분의 GC 알고리즘에서 Minor GC라고 표현한다. 

 

그런데 Young 영역만 청소하다 보면 어느 순간 계속해서 survivor -> old로 이사온 애들이 쌓이기 마련이다.

만약 old 영역이 꽉 차게 된다면 GC는 Young + Old 영역을 둘 다 청소하는 Major GC를 수행한다.

비교적 Minor GC보다 무거운 작업이다.

 

 

위 영역은 단순하게 Old 영역을 나타낸다고 가정하자. 빨간색으로 표시 된 부분은 더 이상 사용되지 않는 객체라고 판단 된 상황이다. 

따라서 Major GC는 빨간 부분을 제거하게 된다.

 

빨간 부분을 제거했고 기존에 빨간 부분이 사용되지 않는 부분(회색)이 되었다. 그런데 이 상태로 냅두면 외부 단편화가 발생하게 된다.

예를 들어 지금 3개의 공간을 사용하는 데이터를 생성해야 하는데 빈 공간들은 3개가 훨씬 넓지만 띄엄띄엄있기에 사용할 수 없다.

 

따라서 이렇게 기존 데이터들을 압축하는 과정(연속적으로 배치)이 필요하게 된다.

 

이렇기에 보통 GC 알고리즘들은 Major GC의 경우 좀 더 무거운 작업이 된다.

물론 GC 알고리즘마다 압축 과정이 없이 다르게 풀 수도 있다.

 

그런데 이러한 GC 알고리즘은 Stop The World를 유발한다.

GC가 수행되는 도중 GC를 처리하는 쓰레드를 제외하고 전부 Stop 시키는 것이다.

 

왜냐하면 GC가 처리되는 도중 애플리케이션이 실행되고 있다면 그 쓰레드가 갑자기 곧 사라질 객체를 참조하거나, 수정하거나

해서 예상치 못한 결과가 발생할 수 있기 때문이다. 또한 위에서 언급된 압축과 같은 과정이 진행된다면 객체의 주소가 바뀌게 되는데

이런 과정들이 다 반영이 되고나서 애플리케이션이 실행되어야 하기 때문이다.

 

따라서 좋은 GC 알고리즘이란 Stop The World 시간을 최대한 줄이는 것 / GC가 적게 발생하는 것 이다.

G1GC Heap

GC를 공부하다 보면 JVM Heap의 논리적인 구조는 GC 알고리즘에 따라 바뀔 수 있다는 것을 알 수 있다.

위에서 설명한 JVM Heap의 논리적인 구조는 G1GC가 주로 사용되기 이전 CMS GC, Parallel GC의 구조라고 볼 수 있다.

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

그러나 G1GC의 경우 Young 영역(eden, survivor)과 Old 영역이 각각 연속적이지 않고, 바둑판 처럼 이곳 저곳 퍼져있는 구조이다.

그렇기에 새로운 객체가 생성되어도 연속적인 메모리 공간에 할당되지 않고 띄엄띄엄 할당될 수 있다는 것이다.

G1GC Cycle

https://docs.oracle.com/en/java/javase/17/gctuning/garbage-first-g1-garbage-collector1.html#GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573

 

G1GC에 대해 찾아보면 위 Cycle 이미지가 자주 나타난다. 그만큼 G1GC에 대해 잘 설명하는 그림인 것 같다.

위에서 표현했던 Minor GC는 Young-only 부분을 의미하게 된다.

G1GC Minor GC (Young GC)

일반적으로 G1GC의 Minor GC(Young GC)은 Eden 영역이 어느정도 많이 차게 되면 실행된다.

다른 GC의 Minor GC처럼 eden -> survivor 하거나 survivor -> old 한다.

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

근데 G1GC는 이런 Minor GC 작업을 처리하면서 여러가지 데이터를 수집하고 계산한다고 한다.

그리고 이러한 계산한 결과를 토대로 Heap 영역에 얼마만큼의 Eden과 Survivor 영역을 할당할지 정하게 된다고 한다.

 

이러한 Minor GC 과정에서 객체들이 이동하기 때문에 Stop The World가 발생하지만 매우 짧은 시간이다.

왜냐하면 일반적인 GC처럼 하나의 연속적인 공간을 다루는 것이 아닌 엄청 쪼개져있는 공간 간의 이동하는 작업이기 때문이다.

G1GC Major GC (Mixed GC)

이렇게 Young GC만 수행하다보면 어느 순간 Old 영역의 데이터가 차게 될 것이다.

만약 사용중인 Old 영역의 공간의 크기가 특정 임계점을 넘게 된다면 G1GC는 Major GC를 시작하게 된다.

 

그리고 G1GC의 Major GC는 여러 단계로 진행이 되며 몇몇 작업들은 애플리케이션이 실행되는 동시에 처리 된다.

Initial Marking

G1GC의 Major GC의 첫 단계는 Initial Marking이다. 이름 그대로 초기 Marking이다.

(GC에서 Marking은 GC 대상이 아니다(살아있는 객체)라고 점 찍어 놓는거다.)

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

그런데 공식문서를 확인해보면 이 단계는 Minor GC(Young GC)에 편승된다는 표현을 한다.

편승이라함은 함께가다. 뭐 이런 느낌인데 한마디로 Minor GC와 같이 진행된다는 의미라고 볼 수 있다.

 

여러가지 내용들을 검색해보고 AI들에게 물어본 내용을 토대로 정리해보자면 G1GC의 쓰레드는 Old 영역이 특정 크기 이상 넘어갔는지

주기적으로 확인하고 있고 만약 넘어갔다면 Major GC를 실행해야한다는 플래그를 설정한다고 한다.

그리고 이 때 Minor GC가 실행된다면 Minor GC는 플래그를 확인하고 Initial Marking을 진행할지 여부를 판단한다고 한다.

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

공식문서에서는 Initial Mark 단계에서 root 지역일 수 있는 survivor 지역을 마킹한다고 한다. 이게 참 어려운 표현이다.

GC에서 root는 객체가 살아있음을 확인할 수 있는 출발점이라고 보면 된다.

 

예를 들어 위 설명처럼 어떠한 survivor 지역의 A 객체가 old 지역의 B객체를 참조하고 있으면

B객체는 사용되고 있음을 알 수 있게 된다. 왜냐하면 A(root) 객체를 확인해보니 B 객체를 참조가 하고 있다는 것을 알 수 있기 때문이다.

 

Initial Mark 단계에서는 Survivor 영역을 포함해서 root가 될 수 있는 대상들을 mark 한다.

 

참고로 Inital Mark 단계는 Minor GC와 함께 동작하기 때문에 Stop The World가 발생된다.

Root Region Scanning

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

두번째는 Root Region Scanning이다. 이 단계는 Inital Mark 단계에서 root라고 마크 된 객체들을 확인하면서 해당 객체가 참조하고

있는 객체들을 쭉 파고 들어 마킹한다. 중요한점은 Stop The World 없이 애플리케이션과 동시에 처리된다는 것이다.

Concurrent Marking Scanning

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

Concurrent Marking 단계는 Heap 영역 전체를 스캔하면서 살아있는 객체들을 Marking 하는 과정이다.

이 과정또한 Stop The World 없이 애플리케이션과 동시에 처리된다.

 

하지만 지금 계속 설명하고 있는 Major GC이 수행되는 도중에도 계속해서 꾸준히 Minor GC(Young GC)가 같이 수행될 수 있다.

만약 Young GC가 수행되어야 하는 경우 Concurrent Marking은 잠시 중지 될 수 있다.

Remark

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

그리고 Remark 단계가 수행된다. 이 Remark 단계는 Concurrent Marking 단계에서 스캔되지 못한 나머지 부분의 영역들을 스캔하고 마킹하는 단계로 아예 완료하기 위해 Stop The World를 걸어버린다.

 

그리고 SATB라는 알고리즘을 사용한다는 표현이 있는데 SATB는 스캔/마킹을 시작하는 시점에 활성화 되어 있는 객체들은 한 Cycle이

끝날때 까지 계속해서 살아있다고 마킹되는 것이다.

 

예를 들어 첫번째 루틴에서 마킹 된 A 객체가 마킹이 끝난 후 갑자기 필요없어졌다 하더라도 두번째 마킹에서 이 객체를 다시

검사하지 않는 것이다. 이렇게 되면 A 객체는 결국 이번 Cycle에서 제거되지 않을 수 있다.

 

이처럼 SATB는 GC의 안정성과 속도를 올리지만 잉여 Garbarge가 남아있을 수 있다.

Copying/Cleanup Phase

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

이전 단계들은 단순히 스캔하고 마킹하는 과정이였다면 이 단계는 실질적으로 Major GC가 물리적으로 행사되는 구간이다.

 

먼저 Cleanup 과정에서는 가장 적은 개수의 생존 객체가 있는 지역부터 정리한다.

왜냐하면 가장 적은 개수의 생존 객체가 있는 지역이라 함은 대피할 객체가 별로 없다는 이야기가 된다.

즉 대피(이동할 데이터)가 별로 없다면 이동 비용이 적기 때문에 Stop The World의 시간이 최소화 된다.

 

Cleanup 과정이 마무리 되었다면 Copying 과정에서는 생존 객체들을 새로운 지역으로 이동시킨다.

 

이 과정에서는 Old 영역뿐만 아니라 Young 영역또한 대상이 되기 때문에 Mixed GC라고 불리기도 하는 것이다.

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

 

이렇게 Cleanup / Copying이 끝나 GC Cycle이 마무리 되면 위 처럼 사용되지 않은 공간은 정리되고 데이터는 압축된다.

정리

 

 

https://docs.oracle.com/en/java/javase/17/gctuning/garbage-first-g1-garbage-collector1.html#GUID-ED3AB6D3-FD9B-4447-9EDF-983ED2F7A573

 

다시 위 Cycle을 확인해보면 이제 이해하기 쉬울 것이다.

 

작은 파란색 원: Minor GC(Young GC)

분홍색 원: Major GC(Mixed GC)

 

비교적 간단한 Minor GC(Young GC)가 수행되다가 Old 영역의 크기가 일정이상 올라갔다.

그래서 큰 파란 원 Minor GC + Initial Mark가 시작되었다.

 

위에서 언급되었듯이 Major GC가 수행되는 도중에도 Minor GC는 수행된다고 했으니 inital mark 이후에도 minor gc는 수행된다.

 

그리고 Concurrent Mark, Remark, Cleanup이 진행된다.

 

그리고 분홍색 원은 GC 대상의 Region에서 살아남은 생존 객체를 이사시키기 위한 Copying 작업이 진행되는 것이다.

 

'java' 카테고리의 다른 글

[JAVA] 제네릭 공변, 반공변  (0) 2025.05.26
[JAVA] Reflection - 리플렉션  (0) 2025.05.24
'java' 카테고리의 다른 글
  • [JAVA] 제네릭 공변, 반공변
  • [JAVA] Reflection - 리플렉션
e4g3r
e4g3r
e4g3r 님의 블로그 입니다.
  • e4g3r
    e4g3r 님의 블로그
    e4g3r
  • 전체
    오늘
    어제
    • 분류 전체보기 (39)
      • spring (22)
      • kotlin (6)
      • java (3)
      • database (3)
      • cs 공부 기록용 (5)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
e4g3r
[JAVA] GC, G1GC
상단으로

티스토리툴바