본문 바로가기

Develop/JAVA

JAVA GC (Garbage Collector) 동작원리 설명

Java Garbage Collector

JVM의 GC는 프로그램 내 사용되지 않는 메모리를 알아서 정리해준다. 따라서 개발자가 직접 메모리 해제를 하지 않아도 된다.

GC는 다음과 같은 2가지 전제('weak generational hypothesis')를 바탕으로 설계되었다.

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

따라서 이러한 전제의 장점을 최대한 살리기 위해서 자바 Heap 메모리는 'Young generation' 영역'Old generation' 영역으로 나누어서 관리한다.

  • Young generation: 새롭게 생성한 객체의 대부분이 여기에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질때 Minor GC가 발생한다고 말한다.
  • Old generation: Young 영역에서 오래 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말한다.

Garbage Collection 과정

가비지 컬렉션은 1) Stop The World, 2) Mark And Sweep 의 2가지 단계를 따른다.

 

1) Stop The World

가비지 컬렉션을 실행하기 위해 JVM이 애플리케이션의 실행을 멈추는 작업이다. GC를 실행하는 쓰레드를 제외한 모든 쓰레드의 작업이 중단된다. 어떠한 GC 알고리즘을 사용하더라도 발생하기 때문에 stop-the-world 시간을 줄이는 게 성능 개선의 관건이다.

 

2) Mark And Sweep

사용되는 메모리와 사용되지 않는 메모리를 식별하고, 마킹되지 않은(사용되지 않는) 메모리를 해제하는 작업이다.

 

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

 

만약 Eden 영역이 가득차면 Minor GC가 동작한다. 이때 살아남은 객체는 Survivor 영역 중 한 군데로 이동한다.

 

다음 번 Minor GC가 동작하게 되면 Eden 영역에서 살아남은 객체+Survivor 영역에 존재하는 객체들은 다른 Survivor 영역으로 이동한다. 따라서 Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 한다. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 현재 시스템이 정상적이지 않은 상황이다.

 

위 과정들이 반복되면서 Young Generation 영역에서 오래 살아남은 객체는 Old Generation 영역으로 이동한다. (Promotion)

 

Promotion이 반복되어 Old Generation 영역이 가득차면 Major GC가 동작한다.

 

만약 Old Generation 영역의 객체가 Young Generation 영역의 객체를 참조하는 경우에는 'Card Table' 이라는 바이트 배열에 그에 대한 정보를 표시한다. 따라서 Minor GC가 일어날 때 Old Generation 영역을 굳이 스캔하지 않아도 Card Table을 통해 GC 대상인지 식별할 수 있다.

Reachability

그렇다면 어떤 방식으로 사용되지 않는 객체를 식별하는 걸까? 자바의 GC는 'Reachability' 라는 개념을 사용한다.

어떤 객체에 유효한 참조가 있으면 'reachable'로, 없으면 'unreachable'로 구별하고, unreachable 객체를 GC의 대상으로 본다. 한 객체는 여러 다른 객체를 참조하고, 참조된 다른 객체들도 마찬가지로 또 다른 객체들을 참조할 수 있으므로 객체들은 참조 트리를 이룬다. 참조 트리에서 유효한 참조 여부를 파악하려면 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 root set이라고 한다.

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

  1. 힙 내의 다른 객체에 의한 참조
  2. Java 스택, Java 메소드 실행 시에 사용되는 지역 변수와 파라미터들에 의한 참조
  3. JNI에 의해 생성된 객체에 대한 참조
  4. 메소드 영역의 정적 변수에 의한 참조

이들 중 1번을 제외한 나머지 3개가 root set으로, reachability를 판가름하는 기준이 된다.