Java – Collection – Map – WeakHashMap (약한 참조 해시맵)

> Weak Reference 

  WeakHashMap의 작동 방식을 이해하려면 JVM의 GC와 관련하여 WeakReference  를 조금은 이해할 필요가 있다.  Java에서는 세 가지 주요 유형의 참조(Reference) 방식이 존재한다.   

  1. 강한 참조 (Strong Reference)
    – Integer prime = 1;   와 같은 가장 일반적인 참조 유형이다.    prime 변수 는 값이 1 인 Integer 객체에 대한 강한 참조 를가진다.  이 객체를 가리키는 강한 참조가 있는 객체는 GC대상이 되지않는다.

     

  2. 부드러운 참조 (Soft Reference)
    – SoftReference<Integer> soft = new SoftReference<Integer>(prime);   와 같이 SoftReference Class를 이용하여 생성이 가능하다.  만약 prime == null 상태가 되어 더이상 원본(최초 생성 시점에 이용 대상이 되었던 Strong Reference) 은 없고 대상을 참조하는 객체가 SoftReference만 존재할 경우 GC대상으로 들어가도록 JVM은 동작한다.   다만 WeakReference 와의 차이점은 메모리가 부족하지 않으면 굳이 GC하지 않는 점이다.  때문에 조금은 엄격하지 않은 Cache Library들에서 널리 사용되는 것으로 알려져있다.

     

  3. 약한 참조 (Weak Reference)
    – WeakReference<Integer> soft = new WeakReference<Integer>(prime);   와 같이 WeakReference Class를 이용하여 생성이 가능하다.  prime == null 되면 (해당 객체를 가리키는 참조가 WeakReference 뿐일 경우) GC 대상이 된다.  앞서 이야기 한 내용과 같이 SoftReference와 차이점은 메모리가 부족하지 않더라도 GC 대상이 된다는 것이다.    다음 GC가 발생하는 시점에 무조건 없어진다.

 

> WeakHashMap

  일반적인 HashMap의 경우 일단 Map안에 Key와 Value가 put되면 사용여부와 관계없이 해당 내용은 삭제되지 않는다.  Map안의 Element들이 일부는 사용되고 일부는 사용되지 않을 수 있는 경우도 있으나, 그것의 구현은 전적으로 프로그래머에 달려있게 된다.  예를 들면 Key에 해당하는 객체가 더이상 존재하지 않게 되는 경우이다.  그래서 만약 어떤 객체가 null이 되어 버리면 해당 객체를 key로 하는 HashMap의 Element도 더이상 꺼낼 일이 없는 경우가 발생하는 것을 가정해볼 수 있다.

  WeakHashMap은 WeakReference의 특성을 이용하여 HashMap의 Element를 자동으로 제거, GC 해버린다.   Key에 해당하는 객체가 더이상 사용되지 않는다고 판단되면 제거한다는 의미이다.    아래의 사용예를 보자.

public class WeakHashMapTest {

    public static void main(String[] args) {
        WeakHashMap<Integer, String> map = new WeakHashMap<>();

        Integer key1 = 1000;
        Integer key2 = 2000;

        map.put(key1, "test a");
        map.put(key2, "test b");

        key1 = null;

        System.gc();  //강제 Garbage Collection

        map.entrySet().stream().forEach(el -> System.out.println(el));

    }
}

결과  (null 로 할당된 key1이 Map 내에서 사라졌다.)

2000=test b

Process finished with exit code 0


이 클래스는 주로 equals 메소드가 == 연산자를 사용하는 Key를 사용할 경우 유용하다.  그런 Key가 버려지면 결코 동일한 Key는 생성되지 않으므로,  나중에 WeakHashMap 에서 해당 키의 조회를 수행하는 것은 불가능하게 되기 때문이다.  그러나 Key가 String 과 같이 단일 값을 가지는 Class고 == 연산자 외에 equals를 사용하여 서로 다른 객체라도 동일성을 체크하여 유지 하고 싶은 경우 적절하지 않다. 

WeakHashMap은 HashMap과 마찬가지로 Thread-safe하지 않으며 필요할 경우 Collections.synchronizedMap 을 이용할 수 있다.


주의점 1 – String은 new가 아닌 리터럴 방식으로 생성될 경우 (String a = “abc”; 와 같이)  intern()에 의해 String pool을 JVM은 사용하므로 key가 null이 된다고 하도 WeakHashMap은 자동으로 삭제되지 않는다.   이는 Integer 와 같은 Class의 (-127 ~128) 값들도 마찬가지 이다.  JVM은 미리 불변 객체로 해당 값을 보관하고 있다.  (Strong 참조)


주의점 2 – WeakHashMap 내의 Value는 통상 강한 참조에 의해 보관 유지된다.  따라서 Value 객체가 직접 또는 간접적으로 자신의 Key를 강한 참조하지 않도록 주의해야한다.  그러면 키가 삭제되지 않기 때문이다.  Value 객체는 WeakHashMap 자체 를 통해 간접적으로 키를 참조 할 수 있다.   만약 Key를 참조하는 Value를 사용하여 WeakHashMap 또한 올바르게 동작하길 원한다면 WeakReferences 내에서 Value를 다시 래핑하는 방식을 써야 한다.  [  예 : m.put (key, new WeakReference (value)) ]