프로그래밍 언어/Java

[Java] Garbage Collection

highright96 2021. 10. 31.

안녕하세요. 오늘은 전에 작성한 JVM 게시글에서 다루지 않았던 자바 Garbage Collections(GC)에 설명드리려고 합니다.

Garbage Collections(GC)는 JVM의 Heap 영역에서 사용하지 않는 객체를 삭제하는 프로세스를 말합니다. 힙 영역에는 인스턴스 또는 객체들을 저장하는 공간입니다. 그럼 GC는 사용하지 않는 객체들을 어떻게 판별해서 삭제할까요?

GC는 어떤 객체에 유효한 참조가 존재하면 'Reachable', 참조가 존재하지 않는다면 'Unreachable'로 구별하고 Unreachable 한 객체를 삭제의 대상으로 봅니다. 객체는 다른 여러 객체들을 참조하고 그 객체도 다른 여러 객체들을 참조하기 때문에 객체는 참조 트리를 이룹니다. 참초 트리에서 유효한 참조인지 확인하기 위해서는 항상 최초의 객체가 존재해야 하는데 이를 'GC Root'라고 합니다.

 

https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/

 

여기서 GC Root가 될 수 있는 조건은 다음과 같습니다.

 

  1. JVM Stack Area의 데이터
  2. Method Area의 static 데이터
  3. Java Native Method(JNI)에 의해 생성된 객체들

 

Garbage Collection의 동작 과정

GC의 기본 프로세스는 'Mark & Sweep' 알고리즘이라고 할 수 있습니다.

 

Mark

GC는 GC Root로부터 모든 객체를 스캔하며 참조되는 객체(Reachable)와 참조되지 않는 객체(Unreachable)를 식별하고 참조되지 않는 객체를 'Mark'합니다.

 

https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

 

Sweep

참조되지 않는 객체(Unreachable)를 힙에서 제거하는 과정입니다.

 

https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

 

Compact

GC의 종류에 따라서 Compact 과정이 추가됩니다. Sweep 후에 분산된 객체를 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나눕니다. 이를 통해 메모리 단편화를 방지합니다.

 

https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

 

본격적으로 GC의 동작 과정에 들어가기 전에 알아두야 할 것이 몇 가지 있습니다.

먼저,  'stop-the-world'라는 용어 입니다. stop-the-world란, GC을 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것을 말합니다. stop-the-world가 발생하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 모두 작업을 멈추게 되고, GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작합니다. 어떤 GC 알고리즘을 사용하더라도 stop-the-world는 발생합니다. 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것을 말합니다.

다음으로 만약, GC가 Heap의 모든 메모리를 뒤져가며 객체를 판별한다면 굉장히 비효율적일 것입니다. 그래서 GC는 'Weak Generational Hypothesis'를 전제로 설계 되었습니다.

 

Weak Generational Hypothesis
1. 대부분의 객체는 금방 접근할 수 없는 상태가 된다.
2. 오래된 객체가 그렇지 않은 객체를 참조하는 일은 거의 없다.


따라서, Heap 메모리는 Generation Garbage Collection 방식을 이용하여 'Young Generation'과 'Old Generation' 영역으로 나누어서 관리합니다.

 

  • Young Generation : 새로운 객체들이 이 영역에 할당됩니다. Eden과 Survival 영역으로 나눠지며, 이 영역에서 발생하는 GC를 'Minor GC'라고 부릅니다.
  • Old Generation : Young Generation 영역에서 오래 살아남은 객체들은 Old 영역으로 'Promoted' 됩니다.
  • MetaSpace : 자바 8부터 등장했으며 자바 7까지는 Permanent generation영역이었으나 제거되었다. 클래스 메타데이터들이 이 영역에 할당된다.

 

https://www.waitingforcode.com/off-heap/on-heap-off-heap-storage/read

 

본격적으로 GC의 동작 과정을 알아보겠습니다.

1. 새로운 객체가 Eden 영역에 할당되고, 더 이상 공간이 꽉 차게 됩니다.

 

우아한테코톡 Youtube 영상

 

2. Eden 영역이 가득 차면 Minor GC(Mark)가 발생합니다. 살아남은 객체는 Survivor 0 영역으로 이동합니다. (Survivor 영역 중 하나로 이동합니다. 0, 1 둘 다 상관없습니다. 다만, 둘 중 하나에 객체가 존재한다면 다른 하나는 비워진 상태여야 합니다.)

 

우아한테코톡 Youtube 영상

 

3. Minor GC(Sweep)가 발생합니다. Eden 영역의 객체(Unreachable 한 객체)는 삭제됩니다.

 

우아한테코톡 Youtube 영상

 

4. Survivor 영역에 있는 객체들의 age가 증가합니다.

 

우아한테코톡 Youtube 영상

 

5. 다시 새로운 객체가 할당되어 Eden 영역이 꽉 차고, Minor GC(Mark)가 발생합니다. 이때 Survivor 0에서 살아남은 객체는 Survivor1으로 이동합니다.

 

우아한테코톡 Youtube 영상

 

6. Minor GC(Sweep)가 발생합니다. Eden 영역의 객체(Unreachable 한 객체)는 삭제됩니다.

 

우아한테코톡 Youtube 영상

 

7. Survivor 영역에 있는 객체들의 age가 증가하고, 객체의 age가 임계점에 도달하면 Old 영역으로 이동합니다. (Promoted)

 

우아한테코톡 Youtube 영상

 

8. Old 영역이 꽉 차면 Major GC가 일어납니다.

 

우아한테코톡 Youtube 영상

 

'Weak Generational Hypothesis'의 전제 덕분에 위와 같이 효율적인 GC 과정이 가능합니다. 그런데 두 번째 전제인 '오래된 객체가 그렇지 않은 객체를 참조하는 일은 거의 없다'의 경우 참조하는 일이 생길 수 있다는 뜻이 되는데, 이럴 경우를 위해 'card table'이라는 JVM이 관리하는 바이트 배열을 두고 있습니다. 오래된 객체가 젊은 객체를 참조할 때는 이 카드 테이블에 정보를 기록하고 Minor GC가 일어날 때 Old 영역을 스캔하지 않고 카드 테이블을 통해 GC 대상인지 식별합니다.

 

Garbage Collection의 종류

Serial GC

Serial GC는 자바 5, 6에서의 기본 GC입니다. 싱글 스레드 환경에 맞게 설계되었습니다. Young 영역과 Old 영역에 대한 GC 과정이 싱글 스레드로 동작하기 때문에 다른 GC에 비해 'Stop The World' 시간이 깁니다. Old 영역에 대한 GC에선 Mark-Sweep-Compact 알고리즘을 사용합니다.

 

Parallel GC

Parallel GC는 자바 8에서의 기본 GC입니다. Young 영역의 GC 과정을 멀티 스레드로 수합니다. 따라서 Serial GC에 비해 'Stop The World' 시간이 줄었습니다.

 

https://coding-start.tistory.com/206

 

Parallel Old GC

Parallel GC에서는 Young 영역에 대해서만 멀티 스레드로 GC를 수행하였는데, Parallel Old GC는 이를 개선하여 Old 영역에 대해서도 멀티 스레드로 수행합니다. Old영역에서 Mark-Sweep-Compact 알고리즘을 사용하는 것을 Mark-Summary-Compact 알고리즘으로 변경했습니다.

 

Summary : 멀티 스레드가 old 영역을 확인합니다.
Sweep : 단일 스레드가 old 영역을 확인합니다.

 

CMS GC

CMS GC는 Old 영역에 대한 GC입니다. Young 영역에서는 Parallel GC와 같은 방식으로 수행됩니다. 

 

https://d2.naver.com/helloworld/1329

 

CMS GC에는 다음과 같은 4개의 과정이 존재합니다.

Initial Mark
GC Root에서 가장 가까운 객체 중 살아 있는 객체만 찾습니다. Reachable 객체들을 전부 찾는 것이 아니기 때문에 Stop-the-world 시간이 짧습니다.

Concurrent Mark
방금 살아있다고 식별된 객체가 참조하고 있는 모든 객체들을 추적합니다. 싱글 스레드로 수행되며 다른 스레드들도 멈추지 않고 동시에 진행됩니다.

Remark
Concurrent Mark 단계에서 식별한 객체를 다시 추적하여 추가되거나 참조가 끊긴 객체를 확인하고 확정합니다. 멀티 스레드로 동작하기 때문에 Stop-The-World 시간이 짧습니다.

Concurrent Sweep
참조되지 않은 객체들을 삭제합니다. 싱글 스레드로 수행되며 다른 스레드와 동시에 수행되기 때문에  Stop-The-World가 발생하지 않습니다.

이러한 단계로 진행되는 GC 방식이기 때문에 Stop-The-World 시간이 매우 짧습니다. 하지만 Compact 과정이 존재하지 않기 때문에 메모리 단편화가 생겨 Concurrent Mode Failure(CMF)가 발생할 수 있습니다. 또한 다른 GC 방식보다 메모리와 CPU를 더 많이 사용합니다. 따라서, CMS GC를 사용할 때에는 신중히 검토한 후에 사용해야 합니다. 그리고 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 Stop-The-World 시간보다 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 합니다.

 

G1 GC(Garbage First)

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

 

G1 GC는 CMS GC를 대체하기 위해 만들어졌습니다. 위 이미지는 G1 Heap 영역에 객체들이 할당된 모습입니다. 다른 GC의 Heap 영역과 전혀 다른 모습을 보여주고 있지만, 동일한 Heap 영역입니다. G1 GC는 기존의 GC와 다르게 힙을 Young, Old 영역을 물리적으로 나누지 않고 일정한 크기의 Region이라는 논리적 단위로 나누어서 관리합니다. CMS GC와 다르게 Compaction 단계를 진행해 메모리 단편화를 없앴습니다.

 

참고

'프로그래밍 언어 > Java' 카테고리의 다른 글

[Java] PermGen과 Metaspace  (0) 2021.11.03
[Java] ClassLoader  (0) 2021.11.02
[Java] JVM  (0) 2021.10.25
자바(Java) 버전별 특징  (0) 2021.10.19
[스터디 할래] 6주차 - 상속  (0) 2021.05.26

댓글