본문 바로가기

스터디/자바

자바 Garbage Collection

Java Garbage Collection

이펙티브 자바의 [아이템7]을 읽고 스터디원들과 얘기하면서 가비지 콜렉터의 메모리 해제 대상이 무엇인지 궁금하게 되었다. 그래서 자바의 가비지 콜렉션에 대해 알아보았다.

Garbage Collection

가비지 콜렉션이란 JVM의 힙 영역에서 사용 중인 객체와 그렇지 않은 객체를 식별하고 사용하지 않는 객체를 삭제하는 프로세스를 말한다. 여기서 사용하지 않는 객체는 어떻게 판별할까? 여기서 자바의 GC는 'Reachability' 라는 개념을 사용한다.

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

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

힙에 있는 객체들에 대한 참조는 4가지 종류 중 하나이다.

  1. 힙 내의 다른 객체에 의한 참조

  2. Java 스택, Java 메소드 실행 시에 사용되는 지역 변수와 파라미터들에 의한 참조

  3. JNI에 의해 생성된 객체에 대한 참조

  4. 메소드 영역의 정적 변수에 의한 참조

1번을 제외한 다른 3개가 'GC Root'로 reachability를 판별하는 기준이 된다.


 

가비지 콜렉션의 기본 프로세스는 'Mark & Sweep' 이라고 할 수 있다.

참조되는 객체와 참조되고 있지않은 객체를 식별하고 참조되는 객체를 Marking 하는 'Mark'

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

마킹된 객체를 제외한 나머지 객체들을 삭제하는 'Sweep' 과정이 있다.

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

추가로 'Compact' 라는 'Sweep' 후 분산되어있는 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 나누는 과정이 있다. 그리고 Compact 과정은 기본적으로 'Stop The World'를 유발한다.

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


본격적으로 가비지 콜렉션 과정에 들어가기에 앞서 알아두어야할 것이 몇가지 있다.

먼저, GC를 실행하면 GC를 실행하는 스레드를 제외한 다른 스레드가 전부 멈추는데 이를 'Stop The World'라 한다. 어떤 GC 알고리즘을 사용하더라도 'Stop The World'는 발생하게 된다.

다음으로 만약, GC가 Heap의 모든 메모리를 뒤져가며 객체를 식별한다면 굉장히 비효율적일 것이다. 그래서 자바의 GC는 'Weak Generational Hypothesis' 의 '대부분의 객체는 금방 접근할 수 없는 상태가 된다' 와 '오래된 객체가 그렇지 않은 객체를 참조하는 일은 거의 없다' 라는 전제를 바탕으로 설계 되었다.

자바의 Heap 메모리는 Generational Garbage Collection 방식을 이용하여 'Young generation' 영역과 'Old generation' 영역으로 나누어서 관리한다.

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

  • Young generation : 새로운 객체들이 이 영역에 할당된다. Eden과 2개의 Survival 영역으로 나누어 지며 Young 영역에서 일어나는 GC 를 'Minor GC'라 한다.

  • Old generation : Young 영역에서 오랫동안 살아남은 객체들은 Old 영역으로 이동하게 된다. 해당 영역에서 일어나는 GC를 'Major GC' 또는 'Full GC'라 한다.

  • MetaSpace : 자바8부터 등장했으며 자바7까지는 Permanent generation영역 이었으나 제거 되었다. 클래스 메타데이터들이 이 영역에 할당된다.

이제 가비지 콜력센의 과정을 알아보자.

   1. 새로운 객체가 Eden 영역에 할당된다.

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

   2. Eden 영역이 가득차면 Minor GC가 일어난다. 살아남은 객체는 Survival 영역 중 한 곳(S0)으로 이동하고 Eden 영역은 빈 상태가 된다.

   3. 다음 Minor GC 가 일어나면 S0과 Eden에서 살아남은 객체들은 다른 Survivor 영역인 S1으로 이동한다. 그리고 S0과 Eden 영역은 빈상태가 된다.

   4. Minor GC가 일어날때마다 살아남은 객체들은 각 서바이벌 영역을 이동하게된다.

   5. 위와 같은 과정이 반복되면서 오랫동안 Young 영역에서 살아남은 객체들은 Old 영역으로 이동하게 되는데 이를 Promotion 이라한다.

   6. Promotion의 반복으로 Old 영역이 가득차게 되면 Major GC가 일어난다.

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

Garbage Collector 의 종류

기본적인 가비지 콜렉션 과정에 대해 알아보았다. 한편 가비지 콜렉터는 다음과 같은 5개의 종류가 존재하고 상황에 따라 필요한 가비지 콜렉터를 선택해서 사용할 수 있다.

  • Serial GC

  • Palleral GC

  • Palleral Old GC

  • CMS GC (Concurrent Mark Sweep)

  • G1 GC (Garbage First)

Serial GC

Serial GC는 Java SE 5, 6 에서의 기본 가비지 콜렉터이다.싱글 스레드 환경에 맞게 설계되었다. Young 영역과 Old 영역에 대한 가비지 콜렉션 과정이 싱글 스레드로 동작하기 때문에 다른 GC에 비해 'Stop The World' 시간이 길다. Old 영역에 대한 GC에선 Mark-Sweep-Compact 알고리즘을 사용한다.

Parallel GC

Parallel GC의 GC과정은 Serial GC와 같지만 Serial 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 알고리즘을 사용한다.

CMS GC (Concurrent Mark Sweep)

CMS GC는 Old 영역에 대한 GC이다. Young 영역에 대해선 Parallel GC와 같은 방식으로 수행한다. CMS GC는 가비지 콜렉션 과정에서 생기는 'Stop The World' 시간을 최소화 하기 위해 설계되었다. 따라서 다른 GC와 다르게 Compaction 과정이 없다.

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

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

  1. Initial Mark

    GC Root에서 가장 먼저 참조하는 객체들만 식별한다. Reachable 객체들을 전부 찾는 것이 아니기 때문에 Stop The World 시간이 짧다.

  2. Concurrent Mark

    싱글 스레드로 수행되며 다른 스레드가 멈추지 않고 동시에 수행된다. Initial mark 단계에서 식별한 객체가 참조하고 있는 모든 객체들을 추적한다.

  3. Remark

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

  4. Concurrent Sweep

    최종적으로 살아있는 객체를 제외한 객체들을 삭제한다.싱글 스레드로 수행되며 다른 스레드와 동시에 수행되기 때문에 Stop The World가 발생하지않는다. 하지만 Compact 과정이 존재하지 않기 때문에 메모리 단편화가 생겨 Concurrent Mode Failure(CMF) 가 발생할 수 있다. Concurrent Mode Failure는 Old영역에 더 이상 메모리를 할당할 공간이 없을 때 발생하는데 메모리 단편화로 인해 Old 영역의 빈 공간이 충분히 존재함에도 불구하고 크기가 큰 객체를 할당할 수 없는 경우가 생길 수 있다. CMF가 발생하면 강제적으로 Compaction을 수행하므로 'Stop The World' 시간이 다른 GC보다 길어질 가능성이 있다.

G1 GC(Garbage First)

G1 GC는 CMS GC를 대체하기 위해 만들어졌다. G1 GC기존의 GC와 다르게 Young 영역과 Old 영역을 물리적으로 나누지않고 힙을 일정한 크기의 Region 이라는 논리적 단위로 나누어서 관리한다. 이 때 G1 GC는 Garbage만 있는 Region을 처음에 수거하기 때문에 Garbage First라는 이름이 붙었다. CMS GC와 다르게 Compaction 단계를 진행해 메모리 단편화를 없앴다.

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

 

G1 힙에 할당된 모습이다. 각 Region은 Eden, Survivor, Old의 공간으로 할당된다. GC후 살아 남은 객체는 다른 Region으로 이동 또는 복사될 수 있다. 또한 Eden,Survivor, Old 이외로 Homongous라는 영역이 존재하는데 이는 객체의 크기가 Region의 50% 보다 큰 경우를 위한 영역이다.

G1의 Young GC

G1에서의 Young GC는 기존의 Minor GC와 비슷하게 동작한다. 멀티 스레드로 GC를 수행하며 Young GC후 살아남은 객체는 Survivor 영역으로 이동하거나 Old 영역으로 이동한다. 이 과정에서 Stop-The-World가 발생하는데 다음 발생할 Young GC를 위해 Eden과 Survivor영역의 크기를 재계산한다.

Young GC후 살아남은 객체들은 기존의 Region이나 새로운 Region으로 이동하는데 이 과정을 Evacuation Pause라고도 한다.

G1의 Old GC

G1의 Old GC는 다음과 같은 5개의 과정이 존재하고 일부 과정은 Young GC 과정의 일부이다.

   1. Initial Mark

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

Initial Mark 단계에선 Survivor 영역에서 Old 영역을 참조하고 있을 수 있는 영역들을 마킹한다. Survivor 영역을 마킹하는 단계이기 때문에 Young GC에 의존적(PiggyBacked)이기 때문에 Stop The World를 발생시킨다.

   2. Root Area Scan

Initial Mark 단계에서 찾은 Survivor Region에 대한 GC 대상을 식별한다. 멀티 스레드로 동작하며 Minor GC가 발생하기 전에 동작을      완료한다.

   3. Concurrent Mark

힙 내의 모든 Old 영역에 대해 GC 대상을 식별한다. 또한 Region에 모든 객체가 Garbage라 판단되면 Remark 단계에서 즉시 제거된        다. Stop-The-World를 발생시키지 않지만 Young GC에 의해 중단될 수 있다.

   4. Remark

Stop The World 구간으로 Concurrent Mark단계에서 GC 대상을 식별하는 것을 완료하고 모든 객체가 Garbage라고 판단된 Region을 제거하고 반환한다. 그리고 각 Region에대한 'liveness' 를 계산한다.Snapshot-At-The-Beginnig(SATB) 알고리즘을 사용하여 CMS GC 보다 속도를 높였다.  SATB 알고리즘이란 Stop The World 이후의 살아있는 객체에만 마킹하는 알고리즘이다.

   5. Copy / CleanUp

Stop The World가 발생하는 단계로 Remark 단계에서 제거한 Region 이외에 liveness가 가장 낮은 Region을 선택하고 새로운 Region 으로 이동 또는 복사한다. 이 과정에선 Old 영역 뿐만아니라 Young 영역도 포함될 수 있다.

  6. After Copy / CleanUp

Copy/Clean Up 단계 이후 새로운 Region으로 이동하고 Compact 되었다.

마치며

Garbage Collection에 대해 여러 사이트를 찾아가며 공부했지만 아직까지도 모자란 부분이 많이 존재한다. 모자란 실력과 얕은 지식으로 공부하고 정리하여 아쉬운 부분이 정말로 많지만 그 과정에서 새롭게 알게된 것도 많이 있었고 개발자로서 코드만 작성하는 것에서 더 나아가 Java에 대해서 관심을 더욱 가지게 된 계기가 되었다.

참고
Java Garbage Collection Basics - https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
Java Garbage Collection - https://d2.naver.com/helloworld/1329
자바 메모리 관리 - 가비지 컬렉션 - https://yaboong.github.io/java/2018/06/09/java-garbage-collection/
Java - Garbage Collection(GC,가비지 컬렉션) 란? - https://coding-start.tistory.com/206
Getting Started with the G1 Garbage Collector - https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
Java 의 GC는 어떻게 동작하나? - https://mirinae312.github.io/develop/2018/06/04/jvm_gc.html
(JVM) Garbage Collection Advanced - https://perfectacle.github.io/2019/05/11/jvm-gc-advanced/
Java Memory Management - https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/
Java - Garbage Collection(GC,가비지 컬렉션) 란? - https://coding-start.tistory.com/206