Java GC가 어떤 방식으로 참조되지 않는 객체를 찾는지 살펴보자
JVM에서 메모리 영역인 런타임 데이터 영역(runtime data area)은 크게 Thread 영역과 Heap영역, Method 영역으로 나눌 수 있다.
이때 Heap 안에 존재하는 객체를 참조하는 방식은 다음과 같다.
- 힙 내의 다른 객체에 의한 참조
- Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
- 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
- 메서드 영역의 정적 변수에 의한 참조
( 아래 그림으로 정리해놓았다 )
이들 중 1번 방식을 제외한 나머지 3개의 참조가 root set으로, reachability를 판가름하는 기준이 된다.
Java GC는 Heap 내 객체 중 더이상 참조되지 않는 객체를 찾는다. 이때 GC는 reachability라는 개념을 사용한다. 어떤 객체에 아직 유효한 참조가 있으면 'reachable'로, 없으면 'unreachable'로 구별하고, unreachable 객체를 garbage로 간주해 GC를 수행한다.
아래 그림을 살펴보면 Heap안에 존재하는 객체를 참조하는 경우와 해당 경우들의 reachable 여부를 확인할 수 있다.
위 그림에서 지역, 정적 변수나 파라미터에 의해 직접적으로 참조되지 않더라도, 참조되고 있는 Heap안 다른 객체에 의해 참조된다면 reachable 하다는 사실을 확인할 수 있다.
이를 좀더 매끄럽게 말하면, root set으로부터 시작하는 참조 사슬에 속한 객체들은 reachable한 객체이고 참조 사슬에 포함되지 않는 객체들은 unreachable한 객체이다.
위 그림에서 참조는 모두 java.lang.ref 패키지를 사용하지 않은 일반적인 참조이며, 이를 흔히 strong reference라 부른다.
java.lang.ref 패키지? strong reference? 이것들은 무엇일까?
간단히 말하자면 GC되는 조건을 셋팅할 수 있게 하는 패키지이다.
GC는 핵심적으로 Heap 내 객체 중에서 더이상 참조되지 않는 객체를 찾아내고 찾아낸 객체 메모리를 회수한다.
초창기 Java에서는 이와 같은 GC의 작업을 개발자가 터치하지 못하도록 구현되어 있었지만 GC를 수행하는 과정에서 좀더 다양한 방법으로 객체를 처리하려는 요구가 있었고, 이에 따라 JDK 1.2부터는 java.lang.ref 패키지를 추가하여 제한적이지만 개발자가 GC와 상호작용할 수 있게 변경되었다.
여기서 말하는 '상호작용'이란 개발자가 직접 참조할 객체 '특성을 선택'하여 참조된 객체가 'GC되는 조건'을 셋팅할 수 있는 것을 말한다.
java.lang.ref 패키지는 위에서 살펴본 전형적인 객체 참조인 strong reference 외에도 soft, weak, phantom 3가지의 새로운 참조 방식을 각각의 Reference 클래스로 제공한다.이 3가지 Reference 클래스를 애플리케이션에 사용하면 앞서 설명하였듯이 GC에 일정 부분 관여할 수 있고, LRU(Least Recently Used) 캐시 같이 특별한 작업을 하는 애플리케이션을 더 쉽게 작성할 수 있다.
java.lang.ref는 soft reference와 weak reference, phantom reference를 클래스 형태로 제공한다. java.lang.ref.WeakReference 클래스는 참조 대상인 객체를 캡슐화(encapsulate)한 WeakReference 객체를 생성한다.
다음은 WeakReference를 사용하는 예제이다
WeakReference<SoonDie> wr = new WeakReference<SoonDie>(new SoonDie());
위에서 생성한 WeakReference 클래스의 객체는 new() 메서드로 생성된 SoonDie 객체를 캡슐화한 객체이다. 여기서 root set으로부터 직접적으로 참조된 WeakReference는 strongly reachable 객체이고 WeakReference에 참조된 SoonDie 객체는 weakly reachable 객체이다.
위에서 살펴본 reachable 기준에 의하면 WeakReference와 SoonDie 모두 root set으로부터 시작하는 참조 사슬에 속하였으므로(어쨋든 둘다 reachable이므로) GC의 대상이 되지 않아야 한다.
*하지만 GC가 동작할 때, unreachable 객체뿐만 아니라 weakly reachable 객체도 가비지 객체로 간주되어 메모리에서 회수한다. 즉 root set으로부터 시작된 참조 사슬에 속해있다 하더라도 개발자의 요구에 의해 객체가 GC의 대상이 될 수 있다는 것이다. 이를 통해 참조는 가능하지만 반드시 항상 유효할 필요는 없는 LRU 캐시와 같은 임시 객체들을 저장하는 구조를 쉽게 만들 수 있다.
++ Java 스펙에서는 SoftReference, WeakReference, PhantomReference 3가지 클래스에 의해 생성된 객체를 "reference object", reference object에 의해 참조된 객체는 "referent"라고 부른다.
위에서 reachability는 reachable, unreachable로 단순하게 나뉘는 것이 아님을 살펴보았다.
Java 스펙에서는 reachability의 종류를 5가지 제시하고 이를 'Strengths of Reachability'라고 부른다.
Java GC는 root set으로부터 시작해서 객체에 대한 모든 경로를 탐색하고 그 경로에 있는 reference object들(예를 들면 WeakReference Object)을 조사하여 그 객체에 대한 reachability를 결정한다. 다양한 참조 관계의 결과, 하나의 객체는 다음 5가지 reachability 중 하나가 될 수 있다.
- strongly reachable : root set으로부터 시작해서 어떤 reference object도 중간에 끼지 않은 상태로 참조 가능한 객체, 다시 말해, 객체까지 도달하는 여러 참조 사슬 중 reference object가 없는 사슬이 하나라도 있는 객체
- softly reachable : strongly reachable 객체가 아닌 객체 중에서 weak reference, phantom reference 없이 soft reference만 통과하는 참조 사슬이 하나라도 있는 객체
- weakly reachable : strongly reachable 객체도 softly reachable 객체도 아닌 객체 중에서, phantom reference 없이 weak reference만 통과하는 참조 사슬이 하나라도 있는 객체
- phantomly reachable : strongly reachable 객체, softly reachable 객체, weakly reachable 객체 모두 해당되지 않는 객체. 이 객체는 파이널라이즈(finalize)되었지만 아직 메모리가 회수되지 않은 상태이다.
- unreachable : root set으로부터 시작되는 참조 사슬로 참조되지 않는 객체
GC가 객체를 처리하는 순서는 다음과 같다.
- soft references
- weak references
- 파이널라이즈
- phantom references
- 메모리 회수
softly reachable
softly reachable 객체, 즉 strong reachable이 아니면서 오직 SoftReferencce 객체로만 참조된 객체는 힙에 남아 있는 메모리의 크기와 해당 객체의 사용 빈도에 따라 GC 여부가 결정된다. 그래서 softly reachable 객체는 weakly reachable 객체와는 달리 GC가 동작할 때마다 회수되지 않으며 자주 사용될수록 더 오래 살아남게 된다.
weakly Reachable
weakly reachable 객체는 특별한 정책에 의해 GC 여부가 결정되는 softly reachable 객체와는 달리 GC를 수행할 때마다 회수 대상이 된다. 앞서 설명한 것처럼 WeakReference 내의 참조가 null로 설정되고 weakly reachable 객체는 unreachable 객체와 마찬가지 상태가 되어 GC에 의해 메모리가 회수된다.
++ GC가 실제로 언제 객체를 회수할지는 GC 알고리즘에 따라 모두 다르므로, GC가 수행될 때마다 반드시 메모리까지 회수된다고 보장하지는 않는다. 이는 softly reachable 객체는 물론 unreachable 객체도 마찬가지이다. GC가 GC 대상인 객체를 찾는 작업과 GC 대상인 객체를 처리하여 메모리를 회수하는 작업은 즉각적인 연속 작업이 아니며, GC 대상 객체의 메모리를 한 번에 모두 회수하지도 않는다.
++ Phantom Reference는 Soft Reference, Weak Reference와 다르게, 사용하기 위함 보다는 올바르게 삭제하고 삭제 이후 작업을 조작하기 위함이다.
공부 자료
Java Reference와 GC _ NAVER D2
Java Phantom Reachable, Phantom Reference 란 _ DevDave