본문 바로가기
Language/자바&코틀린

Garbage Collection의 동작 방식과 종류

by 코딩공장공장장 2024. 7. 6.

Garbage Collection

JVM의 가비지 컬렉터가 불필요한 메모리 자원을 해제하는 작업

 

자바에서 명시적으로 불필요한 데이터를 표현하기 위해서 null을 선언할 수 있다. 허나, 이러한 패턴의 코드는 잘 보이지 않는다. 

JVM의 가비지 컬렉터가 객체들의 참조여부를 체크하여 객체가 점유한 메모리를 해제 시켜주므로 자바 코드에서는 메모리 자원을 해제하는 코드가 잘 보이지 않는다.

(System.gc()를 이용해 호출할 수 있지만, 시스템의 성능에 안 좋음)

 

 

용어 구분 

* 가비지 컬렉터 : 메모리 자원을 할당하고 해제(GC)하는 프로그램

Garbage Collection의 동작 방식

기본 개념

가비지 컬렉션은 Reachable이라는 대상 객체를 참조하는 다른 자원이 있는지 여부 제거 대상을 판단한다.

가비지 컬렉션의 대상인 힙 영역의 객체들은 스택 영역의 변수들이나 참조타입의 정적 필드 의해 참조된다.

이렇게 스택이나 정적 필드에 의해 참조되는 객체를 Reachable이라고 하고, 참조되지 않는 객체는 UnReachable이라고 한다.

가비지 컬렉션은 UnReachable이라고 판단한 객체는 재사용 가능성이 낮다고 생각하여 해당 객체의 메모리 점유를 해제시킨다.

 

Mark & Sweep

가비지 컬렉션은 스택의 변수나 정적필드로부터 참조되는 객체를 시작점(Root Space)으로 참조하는 다른 객체들을 Mark 한다.

이렇게 해서 Mark한 객체들은 Reachable이 되고 마킹 되지 못한 객체들은 UnReachable이 되므로 UnReachable 객체의 메모리 자원을 해제하는 Sweep 과정을 실행한다.

 

Mark Sweep Compact

Mark & Sweep 이후에 Compact 라는 과정을 진행하는 방식으로 Compact는 메모리 단편화를 해결하기 위한 작업이다.

메모리 단편화 메모리 할당 및 해제 작업의 반복으로 중간중간 작은 메모리가 존재하지만 연속적으로 존재하지 않아
새로운 자원을 할당하지 못하는 경우를 말한다.

Compact 작업에서는 Reachable로 살아 남은 객체들을 Heap의 시작 주소로 모아 연속적으로 재배치하여 메모리 단편화 문제를 해결한다.

 

Mark Summary Compact

Mark Sweep Compact의 Sweep 대신 Summary를 하는 방식이다.

Old 영역에서 사용되며 Old 영역을 여러 영역으로 나눈뒤 병렬로 GC스레드가 Mark 작업을 하여 Reachable 객체의 밀도의 통계를 낸다.

각 리전 안에서 밀도가 높은 영역과 밀도가 낮은 영역을 구분하며 밀도가 낮은 영역의 객체는 제거된다.

 

Stop The World

JVM이 가바지 컬렉션을 실행하는 스레드를 제외한 다른 모든 스레드의 작업을 중단 시키고 UnReachable 객체를 제거한다.

Stop the World에서는 Mark & Sweep, Mark Sweep Compact, Mark Summary Compact 등이 쓰일 수 있으며,

가비지 제거에 사용되는 알고리즘을 얘기하는 것이 GC 스레드를 제외한 나머지 스레드를 중단 시키는 알고리즘이 Stop the World이다.

Stop the World의 경우 힙 영역의 사이즈가 클 수록 부하가 커지는 작업이다.

 

 

Heap Space별 GC

먼저 JVM의 Heap 영역이 Young과 Old로 나뉘게 된 배경을 보자.

  • 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)가 된다.
  • 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.

위와 같은 이유로 Young 영역과 Old 영역으로 나뉘었고 Young 영역의 사이즈를 Old 보다 작게 유지한채 Young 영역에 집중해서
메모리 할당과 해제가 이뤄지도록 하였다.

 

Young 영역 -> Minor GC

Young 영역에서 사용되는 GC는 Minor GC이다.

Young 영역은 최초 객체가 할당 되는 Eden 영역과 최소 1번 이상 살아남은 Survivor 영역이 존재한다.

대부분의 객체는 생성 이후 다른 객체에 의해 참조될 일이 없으므로, 많은 객체가 Young 영역에 생성되었다가 사라진다.

 

Minor GC는 Eden 영역이 꽉차면 실행된다. 

Mark & Sweep 방식으로 동작하며 살아남은 객체를 Survivor0으로 보낸다.

이 과정을 반복하다 Survivor0 마저 가득 차게 되면 Survivor1으로 보낸다.

Survivor 영역은 반드시 한 영역만 사용되며 사용되지 않은 영역은 비어있는 상태가 된다.

이 과정을 지속적으로 실행하며 살아남은 객체들이 Old 영역으로 이동(Promotion)하게 된다.

객체의 age

Survivor 영역에서 객체가 살아남은 횟수를 age라고 하며 age 값이 임계값에 도달하면 Old 영역으로 이동하게 된다.
HotSpot JVM의 경우 임계값이 31로 설정되어있다.(age는 Object Header에 기록됨)

 

Old 영역 -> Major GC

Old 영역은 Young 영역에서 GC에 의해 제거되지 않고 살아남은 객체가 복사되는 영역으로 Major GC의 처리를 받는다.

Old 영역의 경우 참조가 많이 되는 객체들이 존재하는 영역이므로 Young 영역에 비해 사이즈가 크다.

영역의 크기가 큰 만큼 GC가 실행되는 시간도 Minor GC에 비해 길다.

또한 Major GC는 GC 스레드를 제외한 모든 스레드 작업을 중단하는 Stop the world 방식을 취한다.

따라서 Old 영역의 Major GC는 애플리케이션에 상당한 부담을 주는 작업니다.

 

 

Garbage Collection 종류

Old 영역의 가비지 수집을 위한 Stop the world의 부하로 가비지 컬레션은 여러 알고리즘이 적용되어 발전되어왔다.

각각의 종류에 대해 알아보자.

 

Serial GC(직렬 가비지 컬렉션)

Serial GC는 Young 영역에서 Mark & Sweep으로 Old 영역에서 Mark Sweep Compact를 사용한다.

Serial GC에서 가비지 컬렉션을 수행하는 스레드는 하나이다.

이는 가비지 컬렉션 작업을 여러 스레드를 통해 병렬로 수행하지 못함을 의미하고
Stop the World 작업이 여러 GC 스레드를 통해 병렬로 수행하는 방식에 비해 오래 걸린다는 것을 의미한다.

 

Serial GC는 CPU 코어가 1개인 경우에만 사용되며, CPU 코어가 여러개이더라도 GC 스레드가 1개이므로
가비지 컬렉션 작업에서 효율성의 차이는 존재하지 않는다.

 

Serial GC는 자바 1.3 이전 버전에서 디폴트로 사용되었다.

 

 

Parallel GC(병렬 가비지 컬렉션)

Parallel GC의 알고리즘은 Serial GC의 알고리즘과 같다. 

다만 GC를 처리하는 스레드가 여러개라는 병렬성의 특징을 갖는다는 것이 큰 차이다.

당연히 GC 작업의 병렬 처리가 가능하므로 Serial GC에 비해 수행 능력이 뛰어나고

자바 1.4부터 1.8까지 디폴트로 사용되었다.

 

 

Parallel Old GC(병렬 Old 영역 가비지 컬렉션)

Parallel GC는 병렬 처리는 Young 영역에서 사용하는 Minor GC에만 적용되었다.

Old 영역에서는 여전히 단일 스레드가 사용되었다.

 

Parallel Old GC는 Mark Summary Compact 방식으로 Old영역에서  병렬처리가 가능해져  Stop the World의 부하를 줄였다.

 

 

CMS GC(Concurrent Mark Sweep)

Concurrent 라는 용어처럼 Mark Sweep을 하는 GC 스레드와 애플리케이션 작업을 하는 스레드가 동시에 작업을 진행한다.

애플리케이션 스레드를 중단하지 않기에 사용중인 객체의 주소를 바꾸는 Compact 작업을 진행할 수 없어 메모리 파편화 문제가 발생한다.

 

CMS 방식은 Stop the World 작업을 줄이기 위한 방식이지만 메모리 파편화 문제로 인해 오히려 비효율이 나타날 수 있고,

결국 자바 9버전 부터 deprecated 되었고 14부터 사용중지 되었다.

 

 

G1 GC(Garbage First)

 

하드웨어의 발전으로 더 큰 메모리가 생겨나게 됬고, 이로 인해 기존의 Young과 Old라는 크게 두 영역으로 나누어진 Heap 영역에서
Root Space 부터 시작해서 각 영역을 모두 탐색하여 mark sweep 과정을 거치는 것은 특히 Old 영역에서 Stop the world에 큰 시간을 초래하게 되었다.

따라서 G1 GC는 Old 영역을 작은 여러개의 균등한 리전으로 나누어 각 영역에서 GC 작업이 이뤄지게 하여 

GC작업이 여러번 일어날 수 있지만 Stop the World 시간을 짧게 가져가 애플리케이션이 GC 작업으로 인한 멈춤 현상을 짧게 가져가도록 처리하였다.

 

 

G1 GC의 특징

 

1. 힙을 여러개의 작은 리전으로 나누어 메모리를 할당하고 해제함
이전의 GC는 Young과 Old 영역의 메모리 할당과 해제의 논리적 개념과 물리적 개념이 동일하였지만,

G1 GC는 Heap 영역을 Young과 Old 역할을 하는 여러개의 작은 리전으로 나누어 메모리 할당과 해제를 진행한다.

이로 인해 GC 작업이 자주 발생할 수 있지만 빠르게 끝낼 수 있어 Stop the World에서 애플리케이션이 멈추는 문제를 해결하는데 큰 도움이 된다.

2. 메모리가 많이 차있는 리전 우선적 GC를 수행

 

여러 리전들 중에서 메모리가 많이 차있는 영역을 우선적으로 GC한다.

메모리가 많은 영역에 가비지가 많을 가능성이 높으니 우선적으로 실행한다.

(수집할 메모리가 적은 영역에 GC를 위해 애플리케이션 스레드를 정지하는 것은 비효율적)

 

3. Available/Unused 영역의 추가

GC 작업에 의해 살아남은 객체들이 다른 리전으로 보내져 비어있는 리전을 Available/Unused라고 한다. 

Available/Unused 리전은 Eden, Survivor, Old가 될 수 있다.

Available/Unused를 필요에 따라 Eden, Survivor, Old으로 활용함으로써 힙 영역을 유연하게 사용할 수 있다.



4. Humogonous 영역의 추가

 

리전의 크기의 50%가 넘는 Humogonous 객체들을 할당하는 공간으로 연속적인 리전의 형태를 띈다.

자바에서 Humogonous 객체를 메모리에 할당과 해제는 일반적인 사이즈의 객체의 메모리 할당과 해제보다 오버헤드가 크다고 한다.

 

현재 G1 GC에서 Humogonous 객체에 대한 최적화 처리는 제공되고 있지 않다고 한다.

오히려 다른 GC보다 성능이 떨어진다고 한다.

 

Humogonous 영역은 단순히 리전이 작게 나누어졌으므로 큰 객체를 할당할 수 있기 위한 존재이지 않나 싶다.

 

 

 

[참고] G1 GC는 자바 9부터 디폴트로 사용되었다.

반응형