얼마 전 N사 전화면접에서 GC에 대한 질문을 받고 그대로 얼어버린 경험이 있다. GC에대해 들어본 적은 있으나 동작원리나 관련 내용들을 제대로 몰라 할 수 있는 말이 없었다. 그래서 이번 포스팅에 대해 GC에 대해 정리해보기로 했다

가비지 콜렉터(Garbage Collector)란?

가비지 콜렉터를 알아보기 전에, 여기서 말하는 가비지가 무엇인지부터 알아보자. 가비지는 '정리되지 않은 메모리', '유효하지 않은 메모리 주소'를 말한다. 다음 코드를 통해 살펴보자

int[] array = new int[2];

array[0] = 0;
array[1] = 1;

array = new String[] {'G', 'C' };

위 코드에서 String 배열이 할당되기 전에 할당한 0과 1은 어디로 갔을까? 이렇게 주소를 잃어버려서 사용할 수 없는 메모리가 '정리되지 않은 메모리'이다. 프로그래밍 언어에서는 Danling Object, 자바에서는 Garbage라고 부른다.

추가로 앞으로 사용하지 않고 메모리를 가지고 있는 객체 역시 Garbage에 포함된다.

가비지는 메모리가 부족할 때 이런 가비지들을 메모리에서 해제 시켜 다른 용도로 사용 할 수 있게 해주는 프로그램을 말한다.

C++와 같은 다른 언어에서는 사용하지 않을 객체의 메모리를 직접 해제해주어야 하지만 자바는 GC가 잡아주니 개발자 입장에서는 편리하다. 다만 모든 메모리 누수를 잡아주는 것은 아님으로 메모리 누수에 대한 경계를 늦추어서는 안된다.

Stop The World

Stop-the-world는 GC 실행을 위해 JVM이 애플리케이션 실행을 멈주는 것이다. GC가 실행 될 때는, GC를 실행하는 쓰레드를 제외한 모든 스레드들이 작업을 멈춘다. GC 작업이 완료한 이후에야 중단했던 작업을 다시 시작한다. 대개의 경우 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것을 말한다.

GC 과정

GC의 과정을 Mark and Sweep이라고도 한다. GC가 스택의 모든 변수 또는 Reachable 객체를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾는 과정이 Mark라고 한다. 이 과정에서 Stop the world가 발생한다. 이후 Mark 되어있지 않은 객체들을 힙에서 제거하는 과정이 Sweep이다.

Minor GC 와 Major GC

JVM의 Heap은 Young, Old, Perm 세 영역으로 나뉜다. Young 영역에서 발생한 GC를 Minor GC, 나머지 두 영역에서 발생한 GC를 Major GC(Full GC)라고 한다.

  • Young 영역 : 새롭게 생성한 객체가 위치, 대부분의 객체가 금방 unreachable 상태가 되기 때문에 많은 객체가 Young 영역에 생성되었다가 사라진다.
  • Old 영역 : Young 영역에서 reachable 상태를 유지해 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다.
  • Perm 영역 : Method Area라고도 한다. 클래스와 메소드 정보와 같이 자바 언어 레벨에서는 거의 사용되지 않는 영역이다.

Youn 영역과 Old 영역, 그리고 GC 동작에 대해 보다 자세하고 알고 싶다면 D2의 Java Garbage Collection를 참고하자

여기서 말하는 reachable은 Stack에서 Heap 영역의 객체에 대해 참조 할 수 있느냐를 애기한다.

Reachability

Java의 GC는 가비지 객체를 판별하기 위해 reachability라는 개념을 사용한다. 어떤 객체에 유효한 참조가 있으면 'reachable', 없으면 'unreachable'로 구별하고 'unreachable'객체를 가비지로 간주한다.

이 부분에 대해 더 알아보고 싶다면 D2의 Java Reference와 GC를 참고하자

바꿔 말하면 객체에 대한 reachability를 제어 할 수 있다면 코드를 통해 Java GC에 일부 관여하는 것이 가능하다. Java에서는 이를 위해서 java.lang.ref 패키지에 SoftReference, WeakReference등을 제공한다.

기본적으로 new로 할당되는 메모리들은 모두 Strong Reference를 가지기 때문에, 캐시와 같은 것을 만든다고 할 때 메모리 누수에 조심해야 한다. 캐시의 키가 원래 데이터에서 삭제가 된다면 캐시 내부의 키와 값은 더 이상 의미가 없는 데이터, 즉 가비지가 된다. 그럼에도 GC는 삭제된 캐시의 키를 가비지로 인식하지 못한다. 이는 캐시에 넣어준 데이터가 Strong Refrence로 독자적인 Reachablity를 가지기 때문이다. 따라서 캐시에 데이터를 넣어 줄 때, 원래 데이터에 Weak Reference를 넣어준다면 이러한 문제를 방지 할 수 있다. Weak Reference는 new로 할당된 객체의 유효 참조를 인위적으로 설정 할 수 있게 해주기 때문에, 원래의 데이터가 삭제되면 이 객체에 Weak Refrence가 걸려있는 객체들은 모두 가비지로 인식된다.

위와 같은 이유로 캐시를 만들고자 할 때는 WeakHashMap을 사용하는 것을 권장한다.

Reference

D2 Java Garbage Collection
D2 Java Reference와 GC
백기선님 - Effective Java Repo
12bme님 - [성능튜닝] 가비지 컬렉터(GC) 이해하기
yaboong님 - 자바 메모리 관리 - 가비지 컬렉션