GC(가비지 컬렉터)

자바는 JVM이 메모리를 관리해준다. 이로써 프로그래머가 동적으로 할당한 메모리 영역을 관리할 필요가 없어진다.

사용하지 않는 메모리를 회수하는 GC 과정을 위해 'stop-the-world' 과정이 필요하다.

stop-the-world란, GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것이다.

stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다.

따라서 실시간 시스템에서 예측 불가능하게 일시적으로 정지할 수 있다.

대개의 GC 튜닝이란 이 stop-the-world 시간을 줄이는 것이다.


HotSpot VM에서는 객체를 관리하기 위해 2개로 물리적 공간을 나누었다.

Young 영역과 Old 영역이다.

Young 영역 : 새롭게 생성한 객체의 대부분이 이 영역에 위치한다. 대부분의 객체가 금방 접근 불가능 상태가 되기 떄문에

매우 많은 객체가 Young 영역에 생성되었다가 사라짐. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다고 말함.

Old 영역 : 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사됨. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(혹은 Full GC)가 발생한다고 말한다.

Young 영역의 구성

- Young 영역은 3개의 영역으로 나뉜다. Eden 영역, Survivor 영역(2개)

  • 새로 생성한 대부분의 객체는 Eden 영역에 위치

  • Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동

  • Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.

  • 하나의 Survivor 영역이 가득 차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 됨.

  • 이 과정을 반복하다가 계속해서 살아남아 있는 객체는 Old 영역으로 이동하게됨.

따라서 Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 한다. 만약 Survivor 영역 모두 데이터가 존재하거나 , 두 영역 모두 사용량이 0이라면 시스템이 비정상적인 상황이다.

GC 방식에는 다양한 종류가 있는데 이는 추후 튜닝이나 필요할 때 찾아보도록 하자.ㅍ

참고 : 가비지 컬렉션(Garbage Collection) 2편arrow-up-right

그러면 Garbage Collector는 어떠한 방식으로 해당 객체가 더 이상 사용되지 않는지 추적할 수 있을까?

JAVA GC는 객체가 가비지인지 판별하기 위해서 reachability라는 개념을 사용한다. unreachable 객체를 가비지로 간주해 GC를 수행한다. 객체들은 서로를 참조하면서 참조 사슬을 이룬다. 이때 유효한 참조 여부를 판단하기 위해 항상 유효한 최초의 참조가 필요한데 이를 객체 참조의 root set이라고 한다.

JVM에서 메모리 영역인 런타임 데이터 영역(runtime data area)의 구조를 그림으로 그리면 다음과 같다.

런타임 데이터 영역은 위와 같이 스레드가 차지하는 영역들과, 객체를 생성 및 보관하는 하나의 큰 힙, 클래스 정보가 차지하는 영역인 메서드 영역, 크게 세 부분으로 나눌 수 있다.

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

  • 힙 내의 다른 객체에 의한 참조

  • Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조

  • 네이티브 스택, 즉 JNI (Java Native Interface)에 의해 생성된 객체에 대한 참조

  • 메서드 영역의 정적 변수에 의한 참조

이들 중 힙 내의 다른 객체에 의한 참조를 제외한 나머지 3개가 root set으로 , reachability를 판가름하는 기준이 된다.

위 그림에서 보듯, root set으로부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고, 이 참조 사슬과 무관한 객체들이 unreachable 객체로 GC 대상이다. 오른쪽 아래 객체처럼 reachable 객체를 참조하더라도, 다른 reachable 객체가 이 객체를 참조하지 않는다면 이 객체는 unreachable 객체이다.

참조의 종류에도 Strong,Soft,Weak,Phantom 다양하게 존재한다.

WeakReference 클래스의 객체로 어떠한 객체를 가리키게 했다고 하자.

Java 스펙에서 SoftReference, WeakReference, PhantomReference 3가지 클래스에 의해 생성된 객체를

"reference object"라고 부른다. 이는 흔히 strong reference로 표현되는 일반적인 참조나 다른 클래스의 객체와는 달리

3가지 Reference 클래스의 객체에 대해서만 사용하는 용어이다. 또한 이들 reference object에 의해 참조된 객체는 "referent"라고 부른다. Java 스펙 문서를 참조할 때 이들 용어를 명확히 알면 좀 더 이해하기 쉽다. 위의 소스 코드에서 new WeakReference() 생성자로 생성된 객체는 reference object이고, new Sample() 생성자로 생성된 객체는 referent이다.

Reference와 Reachability

앞에서 설명한 것처럼, 원래 GC 대상 여부는 reachable인가 unreachable인가로만 구분하였고 이를 사용자 코드에서 관여할 수 없었다. 하지만 java.lang.ref 패키지를 이용해 위의 구분과 같이 더 자세히 구별하게 할 수 있고 , GC 때의 동작을 지정하게 할 수 있게 되었다. 다시 말해 GC 대상 여부를 판별하는 부분에 사용자 코드가 개입할 수 있게 된 것이다.

녹색으로 표시한 중간의 두 객체는 WeakReference로만 참조된 weakly reachable 객체이고, 파란색 객체는 strongly reachable 객체이다. GC가 동작할 때, unreachable 객체뿐만 아니라 weakly reachable 객체도 가비지 객체로 간주되어 메모리에서 회수된다. root set으로부터 시작된 참조 사슬에 포함되어 있음에도 불구하고 GC가 동작할 때 회수되므로, 참조는 가능하지만 반드시 항상 유효할 필요는 없는 LRU 캐시와 같은 임시 객체들을 저장하는 구조를 쉽게 만들 수 있다.

위 그림에서 WeakReference 객체 자체는 weakly reachable 객체가 아니라 strongly reachable 객체이다. 또한, 그림에서 A로 표시한 객체와 같이 WeakReference에 의해 참조되고 있으면서 동시에 root set에서 시작한 참조 사슬에 포함되어 있는 경우에는 weakly reachable 객체가 아니라 strongly reachable 객체이다.

GC가 동작하여 어떤 객체를 weakly reachable 객체로 판명하면, GC는 WeakReference 객체에 있는 weakly reachable 객체에 대한 참조를 null로 설정한다. 이에 따라 weakly reachable 객체는 unreachable 객체와 마찬가지 상태가 되고, 가비지로 판명된 다른 객체들과 함께 메모리 회수 대상이 된다.

LRU 캐시와 같은 어플리케이션에서는 softly reachable 객체보다는 weakly reachable 객체가 유리하므로

LRU 캐시를 구현할 때에는 대체로 WeakREference를 사용한다.

출처 👍

https://d2.naver.com/helloworld/1329arrow-up-right

https://d2.naver.com/helloworld/329631arrow-up-right

Last updated