LRU(Least Recently Used)는 페이지를 사용하는 운영체제에서, 새로운 페이지를 할당하기 위해서 마지막에 사용한 페이지를 교체하는 알고리즘이다. LRUCache는 LRU 알고리즘을 이용하는 캐시클래스라고 보면 되겠다. 여기에서는 많이 사용하는 LRUCache 형태를 살펴보고, 이 형태가 가지고 있는 문제(메모리 릭)를 해결해 보자.
1. LRUCache 구현 방법
1.1 LinkedHashMap을 사용해서 만들기
final int MAX_ENTRIES = 100;
Map cache = new LinkedHashMap(MAX_ENTRIES+1, .75F, true) {
// This method is called just after a new entry has been added
public boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
};
1.2 LinkedHashMap을 상속해서 만들기
대체로 아래의 코드가 많이 보이다. 얼핏 보기에는 잘 동작할 것으로 보인다. 하지만 시간이 지나면서 문제(메모리 릭)가 발생하게 된다.
public class LRUCache<K,V> extends LinkedHashMap<K,V> {
private int lruSize;
public LRUCache(int size) {
lruSize = size;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > lruSize;
}
}
2. 해결책
해결책을 찾아보니, https://code.google.com/p/reflection-dsl/source/browse/trunk/ReflectionDsl/src/br/com/bit/ideias/reflection/cache/LRUCache.java?spec=svn204&r=204 에서는 Value에 해당하는 객체를 SoftReference(참고로, 이넘으로 객체를 래핑하게 되면 풀 GC가 읽어날때 GC대상이 된다)로 래핑해서 GC에게 위임(?)하고 있네요..
개인적으로는 명시적으로 객체를 삭제해서 메모리릭을 방지하는게 좋다고 생각한다. 잠재적인 메모리릭을 방지해 주는 코드는 아래에서 볼 수 있다.
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
boolean isRemove = size() > maxsize;
if (isRemove) {
Object obj = this.get(eldest.getKey());
if(obj instanceof BufferedWriter) { // V가 BufferedWriter 인스턴스임..
try {
((BufferedWriter)obj).close();
} catch (IOException e) {
e.printStackTrace();
}
}
this.remove(eldest.getKey());
}
return isRemove;
}
이상, 자바에서 LRU 형태의 캐시클래스를 만들 때 주의가 필요한 내용을 살펴봤다.