program story

Java의 WeakHashMap 및 캐싱 : 값이 아닌 키를 참조하는 이유는 무엇입니까?

inputbox 2020. 11. 23. 08:05
반응형

Java의 WeakHashMap 및 캐싱 : 값이 아닌 키를 참조하는 이유는 무엇입니까?


Java의 WeakHashMap 은 종종 캐싱에 유용한 것으로 인용됩니다. 약한 참조가 값이 아닌 맵의 키로 정의된다는 것은 이상하게 보입니다. 내 말은, 캐시하고 싶은 값이고, 캐시 외에 다른 누구도이 값을 강력하게 참조하지 않으면 가비지 수집을 원합니다.

키에 대한 약한 참조를 유지하는 데 어떤 방식으로 도움이됩니까? 를하면 ExpensiveObject o = weakHashMap.get("some_key")호출자가 더 이상 강력한 참조를 보유하지 않을 때까지 캐시가 'o'를 유지하기를 원하며 문자열 개체 "some_key"에 대해서는 전혀 신경 쓰지 않습니다.

내가 뭔가를 놓치고 있습니까?


WeakHashMap 적어도 대부분의 사람들이 생각하는 방식으로는 캐시로 유용 하지 않습니다 . 당신이 말했듯이, 그것은 약한 값이 아닌 약한 를 사용하므로 대부분의 사람들이 그것을 사용하고 싶어하는 용도로 설계되지 않았습니다 (사실 사람들이 그것을 잘못 사용하는 것을 보았습니다 ).

WeakHashMap은 수명주기를 제어 할 수없는 객체에 대한 메타 데이터를 유지하는 데 주로 유용합니다. 예를 들어, 클래스를 통과하는 많은 객체가 있고 범위를 벗어 났을 때 알림을받을 필요가없고 참조없이 해당 객체에 대한 추가 데이터를 계속 추적하려는 경우입니다.

간단한 예 (그리고 이전에 사용한 적이있는 예)는 다음과 같습니다.

WeakHashMap<Thread, SomeMetaData>

시스템의 다양한 스레드가 수행하는 작업을 추적 할 수있는 위치 스레드가 죽으면 항목이 맵에서 자동으로 제거되고 스레드에 대한 마지막 참조 인 경우 스레드가 가비지 수집되는 것을 방지 할 수 없습니다. 그런 다음 해당 맵의 항목을 반복하여 시스템의 활성 스레드에 대한 메타 데이터를 찾을 수 있습니다.

캐시가 아닌 WeakHashMap을 참조하십시오 ! 자세한 내용은.

원하는 캐시 유형에 대해 전용 캐시 시스템 (예 : EHCache )을 사용하거나 google-collections의 MapMaker 클래스를 살펴보세요 . 뭔가

new MapMaker().weakValues().makeMap();

당신이 원하는 것을 할 것입니다. 또는 당신이 멋지게 만들고 싶다면 시간 만료를 추가 할 수 있습니다.

new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();

의 주요 용도 WeakHashMap는 키가 사라질 때 사라지고 싶은 매핑이있을 때입니다. 캐시는 그 반대입니다. 값이 사라지면 사라지고 싶은 매핑이 있습니다.

캐시의 경우 원하는 것은 Map<K,SoftReference<V>>. A SoftReference는 메모리가 부족할 때 가비지 수집됩니다. ( WeakReference이를 참조 대상에 대한 하드 참조가 더 이상없는 즉시 지워질 수 있는와 대조하십시오 .) 참조가 캐시에서 소프트되기를 원합니다 (최소한 키-값 매핑이 오래되지 않는 경우). ), 이후 나중에 찾을 경우 값이 여전히 캐시에있을 가능성이 있습니다. 대신 참조가 약한 경우 값이 즉시 gc되어 캐싱 목적이 무효화됩니다.

편의를 위해, 당신은 숨길 수 SoftReference귀하의 내부 값을 Map캐시 타입의 것으로 나타나도록 구현 <K,V>대신 <K,SoftReference<V>>. 그렇게하려면 이 질문 에 인터넷에서 사용할 수있는 구현에 대한 제안 있습니다.

참고 또한 사용할 때 SoftReferenceA의 값을 Map, 당신이 있어야합니다 수동으로 있었다 키 - 값 쌍을 제거하려면 뭔가 할 SoftReferences그렇지 않으면 --- 지워 당신은 Map단지 영원히 크기가 증가하고, 누수 메모리 것입니다.


고려해야 할 또 다른 사항은 Map<K, WeakReference<V>>접근 방식 을 취 하면 값이 사라질 수 있지만 매핑은 그렇지 않다는 것입니다. 사용법에 따라 결과적으로 Weak References가 GC 처리 된 많은 항목을 포함하는 Map이 표시 될 수 있습니다.


두 개의 맵이 필요합니다. 하나는 캐시 키와 약한 참조 값을 매핑하고 다른 하나는 약한 참조 값과 키를 반대 방향으로 매핑합니다. 그리고 참조 대기열 과 정리 스레드 가 필요합니다 .

약한 참조는 참조 된 객체에 더 이상 액세스 할 수 없을 때 참조를 대기열로 이동할 수있는 기능이 있습니다. 이 큐는 정리 스레드에 의해 비워 져야합니다. 그리고 정리를 위해 참조 용 키를 가져와야합니다. 이것이 두 번째 맵이 필요한 이유입니다.

다음 예제는 약한 참조의 해시 맵을 사용하여 캐시를 생성하는 방법을 보여줍니다. 프로그램을 실행하면 다음과 같은 출력이 표시됩니다.

$ javac -Xlint : 체크되지 않은 Cache.java && 자바 캐시
{짝수 : [2, 4, 6], 홀수 : [1, 3, 5]}
{짝수 : [2, 4, 6]}

첫 번째 줄은 홀수 목록에 대한 참조가 삭제되기 전의 캐시 내용과 확률이 삭제 된 후의 두 번째 줄을 보여줍니다.

다음은 코드입니다.

import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Cache<K,V>
{
    ReferenceQueue<V> queue = null;
    Map<K,WeakReference<V>> values = null;
    Map<WeakReference<V>,K> keys = null;
    Thread cleanup = null;

    Cache ()
    {
        queue  = new ReferenceQueue<V>();
        keys   = Collections.synchronizedMap (new HashMap<WeakReference<V>,K>());
        values = Collections.synchronizedMap (new HashMap<K,WeakReference<V>>());
        cleanup = new Thread() {
                public void run() {
                    try {
                        for (;;) {
                            @SuppressWarnings("unchecked")
                            WeakReference<V> ref = (WeakReference<V>)queue.remove();
                            K key = keys.get(ref);
                            keys.remove(ref);
                            values.remove(key);
                        }
                    }
                    catch (InterruptedException e) {}
                }
            };
        cleanup.setDaemon (true);
        cleanup.start();
    }

    void stop () {
        cleanup.interrupt();
    }

    V get (K key) {
        return values.get(key).get();
    }

    void put (K key, V value) {
        WeakReference<V> ref = new WeakReference<V>(value, queue);
        keys.put (ref, key);
        values.put (key, ref);
    }

    public String toString() {
        StringBuilder str = new StringBuilder();
        str.append ("{");
        boolean first = true;
        for (Map.Entry<K,WeakReference<V>> entry : values.entrySet()) {
            if (first)
                first = false;
            else
                str.append (", ");
            str.append (entry.getKey());
            str.append (": ");
            str.append (entry.getValue().get());
        }
        str.append ("}");
        return str.toString();
    }

    static void gc (int loop, int delay) throws Exception
    {
        for (int n = loop; n > 0; n--) {
            Thread.sleep(delay);
            System.gc(); // <- obstinate donkey
        }
    }

    public static void main (String[] args) throws Exception
    {
        // Create the cache
        Cache<String,List> c = new Cache<String,List>();

        // Create some values
        List odd = Arrays.asList(new Object[]{1,3,5});
        List even = Arrays.asList(new Object[]{2,4,6});

        // Save them in the cache
        c.put ("odd", odd);
        c.put ("even", even);

        // Display the cache contents
        System.out.println (c);

        // Erase one value;
        odd = null;

        // Force garbage collection
        gc (10, 10);

        // Display the cache again
        System.out.println (c);

        // Stop cleanup thread
        c.stop();
    }
}

참고 URL : https://stackoverflow.com/questions/1802809/javas-weakhashmap-and-caching-why-is-it-referencing-the-keys-not-the-values

반응형