자바 레퍼런스와 가비지 컬렉션
자바의 가비지 컬렉터는 그 동작 방식에 따라 매우 다양한 종류가 있지만, 기본적으로 모든 자바 가비지 컬렉터는 공통적인 2가지 작업을 수행한다고 볼 수 있다.
1. 힙 내의 객체 중에서 가비지를 찾아낸다.
2. 찾아낸 가비지를 회수해서 메모리 공간을 확보한다.
초창기의 자바에서는 이 gc의 작업에 사용자가 개입하지 못하도록 구현이 되어 있었다. 그러나, JDK 1.2 버전 부터는 java.lang.ref 패키지 내의 클래스들을 이용해서 가비지 컬렉션에 사용되는 레퍼런스에 개입할 수 있도록 구현을 하면서, 자바 가비지 컬렉터와 사용자 사이의 상호작용이 가능하게 만들었다.
이 java.lang.ref 패키지 내에는 총 4가지 종류의 레퍼런스 클래스가 있다. 전형적인 객체 참조의 형태인 strong reference와 weak, phantom, soft 등의 참조 방식을 추가하고, 그에 해당하는 레퍼런스 클래스들을 사용자들에게 제공했다.이 레퍼런스 클래스들을 적절히 사용하게 되면, 앞서 말했듯이 자바 가비지 컬렉션의 과정에 일정 부분 간섭을 할 수 있게 된다.
1. GC와 reachability
자바는 가비지 컬렉션을 위해서 reachability, 즉 접근성이라는 개념을 사용한다. 어느 객체가 다른 객체나 레퍼런스 변수에 의해서 참조가 되어 있으면 reachable 이라고 한다. 반대로, 그 어떤 객체나 변수에 의해서 참조되지 않은 객체는 unreachable 상태라고 부른다.
레퍼런스 변수가 객체를 참조하듯, 각 객체는 필드등을 통해서 다른 객체를 참조할 수 있다. 그렇게 한 객체가 다른 객체를 참조하면서 참조 사슬을 이루게 된다. 이 참조 사슬을 통해서 유효한 참조들을 확인하기 위해서는 참조 사슬의 시작점이 있어야 하는데, 이 시작점을 루트 셋 (root set) 이라고 한다.
JVM 의 메모리 구조를 나타내면 대략 다음과 같은 형태를 갖는다.
런타임 데이터 영역은 위와 같이 스레드가 차지하는 영역들과, 객체를 생성 및 보관하는 하나의 큰 힙, 클래스 정보가 차지하는 영역인 메서드 영역, 크게 세 부분으로 나눌 수 있다. 위 그림에서 객체에 대한 참조는 화살표로 표시되어 있다.
힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나이다.
- 힙 내의 다른 객체에 의한 참조
- Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
- 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
- 메서드 영역의 정적 변수에 의한 참조
이들 중 "힙 내의 다른 객체에 의한 참조"를 제외한 나머지 3개의 방법이 바로 root set으로 reachability를 판가름하는 기준이 된다.
reachability에 대해서 더 알아 보기 위해서 다음 이미지를 보도록 하자
위 그림에서 보듯, root set으로부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고, 이 참조 사슬과 무관한 객체들이 unreachable 객체로 GC 대상이다. 오른쪽 아래 객체처럼 reachable 객체를 참조하더라도, 다른 reachable 객체가 이 객체를 참조하지 않는다면 이 객체는 unreachable 객체이다.
이 그림에서 참조는 모두 java.lang.ref 패키지를 사용하지 않은 일반적인 참조이며, 이를 흔히 strong reference라 부른다.
2. Soft, Weak, Phantom Reference
java.lang.ref는 soft reference와 weak reference, phantom reference를 클래스 형태로 제공한다. 예를 들면, java.lang.ref.WeakReference 클래스는 참조 대상인 객체를 캡슐화(encapsulate)한 WeakReference 객체를 생성한다. 이렇게 생성된 WeakReference 객체는 다른 객체와 달리 Java GC가 특별하게 취급한다(이에 대한 내용은 뒤에서 다룬다). 캡슐화된 내부 객체는 weak reference에 의해 참조된다.
다음은 WeakReference 클래스가 객체를 생성하는 예이다.
i.e.
WeakReference<Sample> wr = new WeakReference<Sample>( new Sample() );
Sample ex = wr.get();
. . .
ex = null;
위 코드의 첫 번째 줄에서 생성한 WeakReference 클래스의 객체는 new() 메서드로 생성된 Sample 객체를 캡슐화한 객체이다. 참조된 Sample 객체는 두 번째 줄에서 get() 메서드를 통해 다른 참조에 대입된다. 이 시점에서는 WeakReference 객체 내의 참조와 ex 참조, 두 개의 참조가 처음 생성한 Sample 객체를 가리킨다.
위 코드의 마지막 줄에서 ex 참조에 null을 대입하면 처음 생성한 Sample 객체는 오직 WeakReference 내부에서만 참조된다. 이 상태의 객체를 weakly reachable 객체라고 한다.
Java 스펙에서는 SoftReference, WeakReference, PhantomReference 3가지 클래스에 의해 생성된 객체를 "reference object"라고 부른다. 이는 흔히 strong reference로 표현되는 일반적인 참조나 다른 클래스의 객체와는 달리 3가지 Reference 클래스의 객체에 대해서만 사용하는 용어이다. 또한 이들 reference object에 의해 참조된 객체는 "referent"라고 부른다. Java 스펙 문서를 참조할 때 이들 용어를 명확히 알면 좀 더 이해하기 쉽다. 위의 소스 코드에서 new WeakReference() 생성자로 생성된 객체는 reference object이고, new Sample() 생성자로 생성된 객체는 referent이다.
- WeakReference 예제 : <http://www.javarticles.com/2016/10/java-weakreference-example.html>
- PhantomReference 예제 : <https://www.concretepage.com/java/example_phantomreference_java>
- SoftReference 예제 : <http://www.baeldung.com/java-soft-references>
'Java > Java Tips' 카테고리의 다른 글
자바의 장단점 (0) | 2018.07.04 |
---|---|
Java 8을 배워야 하는 이유 (0) | 2018.06.28 |
AutoClosable에서 이용해서 리소스 사용 예외 처리하기 (0) | 2018.06.27 |