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 형태의 캐시클래스를 만들 때 주의가 필요한 내용을 살펴봤다.