본문

java.lang.ref #2. GC와 Reference Object/Queue

JDK 1.2버전 이후부터 Reference Object가 등장하였다. 이 API는 객체를 가리키는 특별한 참조를 가지도록 하는것이며, 이를 통해 프로그램과 GC를 연계할 수있다. 물론 모든 프로그램이 이런 기능을 요구하진 않지만, 메모리에 많은 객체를 가지는 경우나, 객체가 반환되기 전에 정리 작업을 수행하는 등의 경우를 위해 Reference Object API가 사용될 수있다. 특히 많은 이미지 객체를 생성하여 제공하는 웹기반 프로그램같은경우, 상황에 맞게 GC의 동작을 제어하는 경우를 생각해볼 수 있다. (메모리가 필요할 때 이미지 캐시를 메모리에서 지울것인가, 혹은 즉시즉시 지울것인가 등..) 


Garbage Collection의 기능

GC가 하는일은, 더이상 사용되지 않는 객체를 찾아서, 이를 메모리에서 제거하는것이다. (이때, 더이상 사용되지 않는다는것의 의미는, 프로그램이 현재 상태에서 객체에 도달하지 못한다는것을 의미한다.) 자바 프로그램은 여러개의 스레드로 이루어져있고, 그 각각은 메소드들을 수행한다. 각각의 메소드는 매개변수와 지역변수를 가지고 이들은 각각의 객체를 가리킨다(=참조). 이러한 참조들은 프로그램에 직접 접근 가능한 '참조들의 루트'에 속한다 할 수 있다. 참조들의 루트에 포함되는 참조들에는 이뿐만 아니라 해당 클래스에 정의된 정적 참조변수와, Java Native Interface(JNI)에서 등록된 참조들이 있다. 


참조들의 루트에 속하는 참조들이 지칭하는 모든 객체들은 '현재, 프로그램에서 접근 가능하다'고 말할 수 있으며, 따라서 GC의 대상이 되지 않는다. 또한, 이 객체들 내에는 다른 객체들을 가리키는 참조가 있을 수 있으며, 그러한 객체들 또한 '접근 가능하다'고 할 수 있다. 하지만, 이 외의, 메모리 힙상에 이들과 분리되어 위치한 객체들은 '도달불가능하다'고 할 수 있으며, 모든 도달불가능한 객체들은 GC의 대상이 된다. 만약 도달불가능한 객체가 finalize() 메소드를 가지고 있다면, 이를 수행하도록 설정 될 것이며, 만약 그렇지 않다면, 혹은 이미 finalizer가 수행된 경우에는, 단순히 GC에 의해 메모리에서 제거된다(reclaimed) - 단, JNI에서 C코드에 의해 할당된 메모리의 경우에는 GC가 이를 처리하지 않는다.


Reference Object를 사용할때 일어나는 일들

Reference Object는 기존의 참조와 어떻게 다를까? Reference Object 상의 참조는 GC에 의해 특별히 관리된다. Reference Object는 다른 객체에 대한 참조(이를 referent(지시대상)이라 한다)를 캡슐화함으로서 생성된다. 아래 소스코드에서는 sr이라는 Soft Reference가 있으며, paint()는 페인트 이벤트가 발생될 때마다 수행된다. 이미 paint()가 한번이상 수행된 경우를 생각해 보자. sr이 null이 아닌경우, sr.get()으로 referent을 가져와서 im에 넣는다. 만약 im이 null이라면(이미 GC됨) getImage()로 이미지를 불러오고, 그 참조를 SoftReference에 넣어 코드를 진행한다. 마지막 줄에 im=null으로 함으로서, 해당 이미지 객체에 대한 참조(strong reference)를 삭제시키지만, 해당 참조는 Soft Reference로 관리되므로 GC는 메모리가 부족하기 전까지는(OutOfMemoryError 오류 발생 전까지) 해당 객체를 삭제하지 않는다. 이러한 관점에서, Soft Reference는 마치 캐시와 같이 사용되며, 따라서 다수의 클라이언트가 접근하는 서버의 경우에 유용하다.


SoftReference sr = null;


public void paint(Graphics g) {

       Image im = (sr == null) ? null : (Image)(sr.get());

       if (im == null) {

            System.out.println("Fetching image");

            im = getImage(getCodeBase(), "truck1.gif");

            sr = new SoftReference(im);

       }

       System.out.println("Painting");

       g.drawImage(im, 25, 25, this);

       im = null

    /* Clear the strong reference to the image */

}


Reference Queues

java.lang.ref.ReferenceQueue는 참조 필드(referent)가 clear 되었을 때(null이 되었을 때), GC가 이를 감지하고 해당 Reference Object를 보관하는 클래스이다. 이를 사용해서 언제 객체가 softly, weakly, phantomly하게 도달가능한지 알 수 있으며, 따라서 각 상황에  따라 대처할 수 있도록 할 수 있다. 예를 들어, phantomly하게 변한(finalizer가 호출된) 객체 내부에 있는, 각각의 객체들에 대해 정리 작업을 수행(예를 들어 자바 메모리 힙 외부에서 사용되는-IO등의- 자원에 대한 정리)할 수 있다.


Reference Queue에 Reference Object가 삽입되기 위해서는, Reference Object를 생성시 Reference Queue를 등록해 주어야 한다. Soft/Weak Reference Object의 경우에는 Reference Queue가 선택적으로 사용되지만, Phantom Reference Object는 생성자에 해당 Reference Queue가 반드시 존재해야 한다.


ReferenceQueue queue = new ReferenceQueue();

PhantomReference pr = new PhantomReference(object, queue);


또다른 방법으로, 앞에서 보았던 SoftReference의 예제에서, Reference Queue를 등록함으로서 해당 referent가 clear된 경우 그에 상응하는 키("truck1.gif")또한 제거되도록 유도할 수 있다(역주: getImage()는 두번째 인자를 key로 하는 HashMap<String, Image>을 retrieve하는것으로 추측된다) 그리고, 프로그램은 Reference Queue에서 remove(), remove(long timeout)을 사용하여 큐에서  Reference Object을 뽑아올 수 있을 때까지 대기할 수도 있다. 


Soft/Weak Reference Object의 경우에는 해당 referent가 clear 되는 경우(=null)에 큐에 삽입되고, Phantom Reference Object의 경우에는 phantomly하게 도달가능할 경우에(referent가 clear 되기 전일 수 있다) 바로 큐에 삽입된다. 이것이 바로, 위에서 언급한, phantom reference object를 통한 정리 작업이 자연스럽게 수행될 수 있는 이유이다.



==============

이 글은 'Reference Objects and Garbage Collection'을 발췌하여 한글로 옮긴것임을 밝힙니다. 또한 원문에는 WeakReference의 예를 비롯하여 좀 더 많은 이야기가 있지만, 전체적인 설명은 이정도로도 충분할 것 같아 이후는 생략합니다. 이전 글인 'java.lang.ref #1. Object Reachability란?'와 함께 보시면 더욱 좋습니다.

댓글

Holic Spirit :: Tistory Edition

design by tokiidesu. powerd by kakao.