본문 바로가기
개발/자바

Java Reference Object의 이해와 활용(strong/weak reference)

by darksilber 2012. 4. 13.
반응형

 

출처 - http://blog.naver.com/atonikkaz/10013450090

strong/weak reference

자바의 garbage collector는 더 이상 참조되고 있지 않는 객체를 자동으로 수거하여 프로그래머가 직접 메모리를 관리하지 않아도 되도록 해준다. 하지만 모든 경우에 대해 garbage collector가 깨끗하게 사용하지 않는 객체들을 정리해주는 것은 아니다. 때로는 참조가 계속 남아있는 경우가 있을 수 있고 이런 경우에 객체는 수거되지 않고 memory leak으로 이어질 수 있다. 이와 같이 참조를 부주의하게 사용하여 발생할 수 있는 memory leak현상을 막기 위해서는 객체에 대한 참조를 유연하게 다루어 필요하지 않은 객체들이 수거될 수 있도록 해야한다.

자바에서는 객체들이 소멸될 시점을 조절하고 객체 소멸에 따른 사후 작업 처리를 위해 reference object들과 reference queue를 지원하는데 이에 대해 알아보도록 하자.

Reachability #

Garbage collector는 reference root set으로 부터 참조를 통해 도달 가능한 객체들을 제외하고는 모두 garbage로 간주한다. 즉 garbage collector의 관점에서는 참조할 수 있는 객체(reachable)와 참조할 수 없는 객체(not reachable)로 나뉘게 된다. Reference object들을 이용하면 reachable한 객체에 대해서 참조에 대한 강약을 조절할 수 있다.

Reference object는 참조의 강약에 따라 Strong reference, Soft reference, Weak reference, Phantom reference로 나뉘고 강약의 세기는 다음과 같다.

 Strong reference > Soft reference > Weak reference > Phantom reference

여러가지 형태로 객체를 동시에 참조할 수 있는데 이 경우 가장 강한 참조가 객체에 대한 참조의 강약을 결정한다. 예를 들어 하나의 객체에 strong reference와 weak reference가 있다면 이 객체에 대한 참조의 강약은 strong reference에 의해 결정되고 이 경우 강한 참조로 도달할 수 있다(strongly reachable)고 볼 수 있다. 이 경우 만약 strong reference가 사라지게 된다면 이 객체의 reachability는 strongly reachable에서 weakly reachable로 바뀌게 된다.

http://dna.daum.net/wiki/imgs/custom/reachability.gif

Reference queue #

Reference object들의 reachability가 변한 후 garbage collector에 의해 수거될 때 finalize()가 호출된 후 그 reference object는 연관된 reference queue에 쌓이게 된다. 이런 식으로 객체가 소멸되어 가기 전에 queue를 한번 거쳐감으로써 객체 소멸에 필요한 로직을 추가할 수 있게 해준다.

Reference objects #

Strong reference #

Strong reference는 new를 이용하여 객체를 생성하면 생기게 되는 참조를 말한며 reachable하고 strong reference에 의해 참조되고 있는 객체는 garbage collection 대상에서 무조건 제외된다. 때문에 자바가 자동으로 메모리 관리를 해준다고는 하지만 memory leak을 방지하기 위해서는 객체가 strong reference되고 있는지 주의해서 볼 필요가 있다.

  • Strong reference를 사용할 경우 memory leak이 나타날 수 있는 경우에 대한 예제 Memory leak이 나타날 수 있는 대표적인 경우는 class variable(static modifier사용)로 Map과 같은 collection을 사용했을 때이다. 다음과 같은 코드를 보자.

import java.util.ArrayList;
import java.util.List;

public class StrongReferenceSample {
private static List stack = new ArrayList();

private static int position;

public static void push(String str) {
stack.add(str);
position++;
}

public static String pop() {
if (position > 0) {
// stack.remove(position);
return stack.get(position--);
}
return null;
}
}

위의 코드는 간단한 String객체를 담을 수 있는 stack을 구현한 것이다. push와 pop 두 가지 기능만을 가지고 있는데 pop() method를 보면 memory leak을 유발할 수 있는 가능성을 내포하고 있다. 다음 그림을 보면 쉽게 이해할 수 있을 것이다.

http://dna.daum.net/wiki/imgs/custom/stack.gif

위의 그림처럼 3개의 데이터를 stack에 넣고(그림1) 그 후 하나의 데이터를 꺼낸 경우<그림2>를 생각해본다면 빨갛게 표시된 3번째 슬롯에서 가리키고 있는 객체는 사용되고 있지 않지만 strong reference가 존재하기 때문에 이 stack에 또 다른 객체를 추가해서 참조를 바꾸거나 명시적으로 참조를 지워주지 않으면 수거되지 않고 계속 살아있게 된다.

Soft reference #

Soft reference는 ''SoftReference''객체에 reference를 넘겨 주어 생성할 수 있다.

SoftReference sr = new SoftReference(new Object());

Soft reference에 의해 참조된 객체는 strong reference와는 다르게 garbage collector에 의해 수거 될 수도 있고 되지 않을 수도 있다. 메모리에 충분히 여유가 있다면 garbage collection이 수행되고 있다 하더라도 softly reachable한 객체는 수거되지 않는다. 하지만 메모리에 여유가 없다면, 좀 더 정확히 말해서 Out of memory 에러를 낼 상황이라면 soft reference에 의해 참조된 객체는 garbage collector에 의해 수거된다.

Weak reference #

Weak reference는 WeakReference객체에 reference를 넘겨주어 생성할 수 있다.

WeakReference sr = new WeakReference(new Object());

Weak reference에 의해 참조된 객체는 garbage collection이 발생하기 전까지는 객체에 대한 참조를 유지하지만 일단 garbage collection이 발생하면 무조건 수거된다. Weak reference가 사라지는 조건은 garbage collection의 실행 주기와 일치하는 것이다. 이를 이용하면 짧은 시간 동안 자주 쓰일 수 있는 객체를 캐쉬할 때 유용하게 이용할 수 있다. 예를 들어 다음과 같은 코드를 보자.

...

WeakReference wr;

public String getFileContent(String filename) {
String fileContent = (wr != null) ? wr.get() : null; // WeakReference에 의해 파일 내용이 보존되어 있는지 체크.
if (fileContent == null) {
fileContent = readFileToString(filename);
wr = new WeakReference(fileContent); // 글 내용을 WeakReference에 저장.
}
return fileContent;
}

...

위의 코드를 보면 파일에서 글을 읽어 weak reference에 저장하고 그 weak reference의 내용을 캐쉬처럼 재활용하는 것을 볼 수 있다. 만일 garbage collection이 발생하지 않았다면 wr.get() 부분에서 글의 내용이 반환될 것이다. 하지만 garbage collection이 일어났다면 wr.get()은 null을 반환하게 될 것이고 데이터를 다시 채우는 부분을 실행하게 될 것이다.

Phantom reference #

Phantom reference는 finalize()가 호출된 후 그 객체와 관련된 사후 작업을 수행할 필요가 있을 때 사용된다.

Phantom reference의 특이한 점은 다시는 객체는 참조할 수 없다는 것이다. 실제로 phantom reference는 참조되는 객체(referent)를 내부적으로 유지하고 있지만 그 객체를 다시 꺼내오는 get() method에서 무조건 null을 반환하도록 되어 있는데 이는 phantom reference가 참조하는 객체는 다시 활용될 수 없고 다만 garbage collector가 수행되면서 그 객체가 사라질때 관련된 사후 작업만을 할 수 있다는 의미이다. Phantom reference에서 객체를 꺼내게 되면 항상 null이 나오기 때문에 객체가 사라졌다고 생각할 수 있으나 실제로는 사라진 것이 아니고 그렇게 보이는 것일 뿐이다. 그래서 phantom reference는 명시적으로 객체를 소멸시켜주는 clear()를 반드시 호출되어야 한다.

참고: ''PhantomReference.get()''

public class PhantomReference extends Reference {
...
public T get() {
return null;
}
}

Reference object의 활용 : simple cache #

Java runtime library에는 이미 weak reference를 이용한 WeakHashMap이 존재하고 garbage collection 시간에 맞춰 자동으로 expire되는 캐쉬로 활용할 수 있다. 이와 비슷한 weak reference를 이용한 캐쉬를 어떻게 구현할 수 있는지 살펴보자.

  • SImpleCache.java

package andy.ref;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

public class SimpleCache {
private Map map = new HashMap();

public void put(String key, Object value) {
map.put(key, new WeakReference(value));
}

public Object get(String key) {
Object obj = map.get(key);
return ((WeakReference) obj).get();
}
}

간단하게 hash의 value를 ''WeakReference'' 로 감싸서 넣어봤다. 아래 unit test를 보면 알겠지만 System.gc() 수행 후 hash의 value가 사라지는 것을 볼 수 있다. 일반적으로 캐쉬 프레임웍의경우 캐쉬 엔트리 수를 제한하고 엔트리가 가득찬 경우나 오래된 정보들을 LRU알고리즘등을 써서 어떻게 교체할 것인지에 대한 많은 고민들을 해야한다. 하지만 위와 같이 reference object들을 이용하면 쉽고 간단하게 비슷한 효과를 낼 수 있다.

주의 : 위의 예제에서는 key와 reference object 자체는 strong reference에 계속 유지되고 있으므로 완벽한 memory leak free는 아니지만 reference queue를 이용하면 필요한 뒷 마무리 작업들을 할 수 있을 것이다.

  • SimpleCacheTest.java

package andy.ref;

import junit.framework.TestCase;

public class SimpleCacheTest extends TestCase {
public void testCache() {
String key = new String("Key");
String value = new String("" + Math.random());

SimpleCache cache = new SimpleCache();
cache.put(key, value);

value = null;

assertNotNull(cache.get(key)); // Garbage collection 이전에는 value가 존재함.
System.gc();
assertNull(cache.get(key)); // value가 사라짐.
}
}

반응형

댓글