언제 WeakHashMap 또는 WeakReference를 사용 하시겠습니까?


163

약한 참조의 사용은 구현을 본 적이없는 것이므로 사용 사례가 무엇인지, 구현이 어떻게 작동하는지 파악하려고합니다. 때 당신은을 사용하는 데 필요한 한 WeakHashMap또는 WeakReference어떻게 사용 되었는가?



답변:


96

강력한 참조의 문제점 중 하나는 캐싱, 특히 이미지와 같은 매우 큰 구조에서 발생합니다. 내가 작업하는 웹 사이트 디자인 도구와 같이 사용자 제공 이미지로 작업해야하는 응용 프로그램이 있다고 가정합니다. 디스크에서 이미지를로드하는 것은 매우 비싸기 때문에 한 번에 두 개의 (잠재적으로 거대한) 이미지 사본이 메모리에있을 가능성을 피하고 싶기 때문에 당연히 이러한 이미지를 캐시하려고합니다.

이미지 캐시는 반드시 필요하지 않을 때 이미지를 다시로드하지 못하도록하기 때문에 캐시에 항상 메모리에있는 모든 이미지에 대한 참조가 포함되어 있어야합니다. 그러나 일반적인 강력한 참조를 사용하면 해당 참조 자체로 이미지가 메모리에 남아있게되므로 이미지가 더 이상 메모리에 더 이상 필요하지 않은 시점을 판별하고 캐시에서 이미지를 제거하여 가비지 콜렉션에 적합하게해야합니다. 가비지 수집기의 동작을 복제하고 객체가 메모리에 있어야하는지 여부를 수동으로 결정해야합니다.

약한 참조 이해 , Ethan Nicholas


43
이 경우 SoftReferences가 더 좋지 않습니다. 즉 메모리가 부족할 때만 수집되는 참조입니다.
JesperE

조금 혼란 스럽습니다 ... SWT 이미지 캐시가 있다고 가정 해 봅시다. SO 자원을 해제하려면 SWT 이미지를 dispose () 메소드를 통해 처리해야합니다. WeakHashMap을 사용하여 저장하면 GC가 객체를 어떻게 처리하는지 정확하게 보여줍니까?
marcolopes

2
@marcolopes GC는 다른 객체에서 종료자를 사용합니다. SWT가 그렇게 할 때 마음에 들지 않는 것처럼 보이므로 운영 체제 리소스를로 관리 할 수 ​​없다고 생각합니다 WeakHashMap.
Jacob Krall

@marcolopes, (귀하의 GC가 메모리를 회수하기 전에 마무리 호출을 보장한다고 가정합니다.) 캐시 마무리 장치에서 처리가 완료되면 모든 것이 정상입니다. dispose가 수동으로 호출해야하는 경우 1) 클래스를 확장하고 종료자를 종료하거나 2) 팬텀 참조를 사용하여 적절하게 dispose를 추적하고 실행합니다. 옵션 2가 더 좋습니다 (부활 버그를 피하고 다른 스레드에서 처리를 실행할 수있는 기능을 제공함). 옵션 1은 도우미 클래스없이 구현하기가 더 쉽습니다.
Pacerier


55

WeakReferenceSoftReference

명확하게 구분할 수있는 한 가지 차이점은 a WeakReference와 a 의 차이 SoftReference입니다.

기본적으로 a WeakReference는 참조 된 객체에 대한 하드 참조 가 없으면 JVM에 의해 간절히 GC-d가 됩니다 . 반면에 d 객체는 실제로 메모리를 회수해야 할 때까지 가비지 수집기에 의해 남겨지는 경향이 있습니다.SoftReference

WeakReferences 안에 들어 있는 캐시 는 쓸모가 없습니다 (a WeakHashMap에서는 약하게 참조되는 키입니다). SoftReferences사용 가능한 메모리로 증가 및 축소 할 수있는 캐시를 구현할 때 값을 감싸는 데 유용합니다.


4
"값이 WeakReferences 안에 들어있는 캐시는 쓸모가 없습니다."나는 전혀 동의하지 않습니다.
Thomas Eding

5
@trinithis-음, 난 정말 무슨 말을 해야할지 모르겠어요. 값을 참조하지 않는 순간 값이 사라지는 캐시가 유용한 이유 는 무엇입니까?
oxbow_lakes

4
메모와 같은 경우 캐시 값을 느슨하게 보유하는 캐시가 유용 할 수 있습니다.
Thomas Eding

5
@ThomasEding 나는 여전히 그것을 얻지 못한다. 캐시가 유용하다고 생각되는 유일한 시점은 다른 참조가 없을 때입니다. 참조가있는 경우 캐시가 무엇입니까?
Cruncher

2
@ThomasEding, Softref는 환경에 "메모리가 없을 때까지 이것을 저장합니다"라고 말합니다. Weakref는 환경에 "GC가 실행될 때까지 이것을 저장한다"고 말합니다. GC 자체를 디버깅 / 프로파일 링하지 않는 한 weakref의 유스 케이스는 없습니다. 메모리에 민감한 캐시를 원하면 softref를 사용하십시오. 캐시를 원하지 않으면 캐시하지 마십시오! weakref는 어디로 오나요?
Pacerier

30

WeakReferences와 WeakHashMaps 의 일반적인 용도 중 하나 는 객체에 속성을 추가하는 것입니다. 경우에 따라 일부 기능이나 데이터를 객체에 추가하려고하지만 하위 클래스 및 / 또는 컴포지션이 옵션이 아닌 경우 추가하려는 속성으로 확장하려는 객체를 연결하는 해시 맵을 만드는 것이 분명합니다. . 그런 다음 속성이 필요할 때마다지도에서 찾을 수 있습니다. 그러나 속성을 추가하는 객체가 많이 파괴되고 생성되는 경향이있는 경우 맵에서 많은 메모리를 차지하는 오래된 객체가 많이 생길 수 있습니다.

WeakHashMap대신에 객체 를 사용하는 경우 객체가 더 이상 프로그램의 나머지 부분에서 더 이상 사용되지 않는 즉시 맵을 떠나게되며 이는 바람직한 동작입니다.

나는 몇 가지 데이터를 추가하기 위해이 작업을 수행했다 java.awt.Component1.4.2과 1.5 사이의 JRE의 변화를 해결하기 위해, 내가 관심 INT (있었다 모든 구성 요소를 서브 클래 싱하여 문제를 해결 한 수 JButton, JFrame, JPanel...) 그러나 이것은 훨씬이었다 훨씬 적은 코드로 쉽게 수행 할 수 있습니다.


1
WeakHashMap은 "다른 프로그램에서 더 이상 사용되지 않습니다"를 어떻게 알 수 있습니까?
Vinoth Kumar CM 2016

2
약한 hasmap은 해당 키에 약한 참조를 사용합니다. 객체가 약한 참조를 통해서만 참조되면 가비지 수집기는 약한 참조의 소유자 (이 경우 WeaHashMap)에 '알림'합니다. javadocs의 WeakReferences 및 ReferenceQueues를 읽고이 객체가 가비지 수집기와 상호 작용하는 방식을 이해합니다.
luke

1
감사합니다 luke, 간단한 설명을 제공 할 수 있습니까?
끓인 물

따라서 일부 클래스를 확장 할 수없는 기발한 API 때문에 Java 에서 약한 참조 가 의미가 있습니다.
Pacerier

22

또 다른 유용한 경우 WeakHashMapWeakReferenceA는 리스너 레지스트리 구현 .

특정 이벤트를 듣고 싶은 것을 만들 때 일반적으로 리스너를 등록합니다.

manager.registerListener(myListenerImpl);

manager리스너를 리스너에 저장하는 경우 리스너 또는 리스너를 보유한 구성 요소를 사용할 수 없게되면 WeakReference레지스터 manager.removeListener(myListenerImpl)가 자동으로 제거되므로 레지스터를 제거 할 필요 가 없습니다.

물론 리스너를 수동으로 제거 할 수는 있지만 잊어 버렸거나 잊어 버린 경우 메모리 누수가 발생하지 않으며 리스너가 가비지 수집되지 않습니다.

WeakHashMap사진은 어디 에서 나오나요?

등록 된 리스너를 저장하는 리스너 레지스트리 WeakReference에는 이러한 참조를 저장하기위한 콜렉션이 필요합니다. WeakHashSet표준 Java 라이브러리 에는 구현 이 없지만 WeakHashMap후자를 쉽게 사용하여 첫 번째 기능을 "구현"할 수 있습니다.

Set<ListenerType> listenerSet =
    Collections.newSetFromMap(new WeakHashMap<ListenerType, Boolean>());

이를 통해 listenerSet새 리스너를 등록하기 만하면 리스너를 세트에 추가하기 만하면됩니다. 명시 적으로 제거되지 않더라도 리스너가 더 이상 참조되지 않으면 JVM에 의해 자동으로 제거됩니다.


10
리스너 목록에 weakHashSets를 사용할 때의 문제점은 register ()로 작성된 익명 리스너 인스턴스가 쉽게 손실되어 사용자가 예상치 못한 것입니다. 대신 관리자의 리스너에 대한 강력한 참조를 보유하는 것이 더 안전하며 대신 올바른 작업을 수행하려면 호출자에게 의존합니다.
Gunanaresh 2016

리스너 레지스트리 구현 : 등록 된 모든 리스너는 다음 GC 킥에서 수집 / 파기됩니다. 예를 들어, 모든 리스너의 onSomethingHappened () 메소드를 실행하는 동안 GC가 시작되면 어떻게됩니까?
blackkara

@icza, 나는 사람들이이 신화를 여전히 믿지 않는다고 믿을 수 없다. 이것은 완전히 틀린 대답입니다. 또 다른 유용한 사례 는 객체 WeakHashMap가 필요할 때마다 있습니다 HashMap. 그래서 와우 수동으로 할 필요가 없습니다 hashmap.remove을 이제까지 하여 obj가 범위 밖으로되면 항목이 자동적으로 제거되기 때문에! 말 그대로 마술! 그런 추악한 마술 해킹은 완전한 페이스 팜 입니다.
Pacerier

3
@Pacerier : JavaScript 토지의 다른 의견에서 귀하의 링크를 여기까지 내려 왔지만 WeakMap을 사용한 리스너 레지스트리 구현이 신화 인 이유를 여전히 이해하지 못합니다. 예를 들어, WebSocket 클라이언트가 레지스트리 서비스를 통해 일부 리스너와 바인딩되어야하는 경우 소켓 오브젝트를 WeakMap에 키로 저장하는 것이 논리적으로 보입니다 (연결 종료 후 (예 : 오류시) 메모리에 매달리지 못하도록) 필요한 경우 모든 리스너를 검색합니다. 그렇다면이 접근 방식에 정확히 어떤 문제가 있습니까?
손상된 유기

1
@Pacerier 나도 당신의 이의를 이해하지 못했습니다. Publish-Subscribe 또는 Event Bus 시나리오에서 약한 참조 모음은 나에게 완벽합니다. 구독 오브젝트는 공식적으로 구독을 취소 할 필요없이 범위를 벗어나 가비지 콜렉션으로 이동할 수 있습니다. 구독 취소 프로세스는 구독자 개체에 대한 지식없이 타사 개체가 초기 구독을 담당 한 경우 특히 복잡 할 수 있습니다. 모음은 WeakReference코드 기반 을 크게 단순화하고 구독 취소 실패와 관련된 불필요한 버그를 피합니다. 어떤 단점이 있습니까?
Basil Bourque

5

이 블로그 게시물은 Java : ID에서 동기화 두 클래스의 사용법을 보여줍니다 . 사용법은 다음과 같습니다.

private static IdMutexProvider MUTEX_PROVIDER = new IdMutexProvider();

public void performTask(String resourceId) {
    IdMutexProvider.Mutex mutext = MUTEX_PROVIDER.getMutex(resourceId);
    synchronized (mutext) {
        // look up the resource and do something with it
    }
}

IdMutextProvider는 동기화 할 id 기반 객체를 제공합니다. 요구 사항은 다음과 같습니다.

  • 동등한 ID를 동시에 사용하려면 동일한 객체에 대한 참조를 반환해야합니다.
  • 다른 ID에 대해 다른 객체를 반환해야합니다.
  • 릴리스 메커니즘 없음 (개체가 제공자에게 리턴되지 않음)
  • 누출되지 않아야 함 (사용하지 않은 객체는 가비지 수집 대상 임)

이는 다음 유형의 내부 스토리지 맵을 사용하여 수행됩니다.

WeakHashMap<Mutex, WeakReference<Mutex>>

객체는 키와 가치입니다. 맵 외부에 객체에 대한 하드 참조가없는 경우 가비지 수집 될 수 있습니다. 맵의 값은 하드 참조와 함께 저장되므로 메모리 누수를 방지하기 위해 값을 WeakReference 에 래핑해야합니다 . 이 마지막 요점은 javadoc 에서 다룹니다 .


3

예를 들어 특정 클래스로 작성된 모든 객체를 추적하려는 경우. 이러한 객체가 가비지 수집되도록하려면 객체 자체 대신 객체에 대한 약한 참조 목록 / 맵을 유지하십시오.

이제 누군가 팬텀 참조를 설명 할 수 있다면 기쁠 것입니다 ...


2
한 가지 용도 : PhantomReferences를 사용하면 객체가 메모리에서 제거 된시기를 정확하게 결정할 수 있습니다. 그들은 실제로 그것을 결정하는 유일한 방법입니다. ( weblogs.java.net/blog/enicholas/archive/2006/05/… )
Jacob Krall

실제로 명시 적으로 지울 때까지 제거되지 않습니다. "부드럽고 약한 참조와 달리 팬텀 참조는 큐에 넣을 때 가비지 수집기에서 자동으로 지워지지 않습니다. 팬텀 참조를 통해 도달 할 수있는 개체는 이러한 참조가 모두 지워지거나 도달 할 수 없을 때까지 남아 있습니다."
jontro

@ jontro, 그러나 그것은 이미 확정되었습니다 , 모든 회원이 사라졌습니다. 효과적으로, 그것은 빈 obj입니다.
Pacerier

3

전술 한 바와 같이, 강한 참조가 존재하는 한 약한 참조가 유지된다.

예를 들어 리스너 내부에서 WeakReference를 사용하면 대상 객체에 대한 기본 참조가 없어지면 리스너가 더 이상 활성화되지 않습니다. 이는 WeakReference가 리스너 목록에서 제거됨을 의미하지는 않으며 정리가 여전히 필요하지만 예를 들어 스케줄 된 시간에 수행 될 수 있습니다. 이것은 또한 수신 된 객체가 강력한 참조를 보유하지 못하게하고 결국 메모리 팽창의 원인이되는 것을 방지하는 효과가 있습니다. 예 : 창보다 수명이 더 긴 모델을 참조하는 스윙 GUI 구성 요소.

위에서 설명한 것처럼 청취자를 가지고 놀면서 우리는 객체가 사용자의 관점에서 "즉시"수집된다는 것을 빠르게 깨달았습니다.


유용한 답변 감사합니다. 그러나 그 경우 청취자가 강하게 등록 (참조)되어야하는지 궁금합니다.
blackkara

이 답변은 완전히 틀 렸습니다.
정교함

@Pacerier- WeakReferences귀하의 의견은 완전히 잘못되었습니다!

2

내가 약한 참조에 실제로 사용했던 한 가지 사례는 거의 사용되지 않는 하나의 매우 큰 객체를 사용하는 것입니다. 필요하지 않을 때 메모리에 보관하고 싶지 않습니다. 그러나 다른 스레드에 동일한 객체가 필요한 경우 두 개의 메모리 중 하나를 원하지 않습니다. 어딘가에 객체에 대한 약한 참조를 유지하고 그것을 사용하는 방법에 대한 하드 참조를 유지할 수 있습니다. 메소드가 모두 완료되면 오브젝트가 수집됩니다.


1
이것은 약한 참조가 아닌 소프트 참조입니다. 참조 stackoverflow.com/a/155492/632951
Pacerier


-1

weakhashmap을 사용하여 광범위한 개체 생성을위한 리소스가없는 캐싱을 구현할 수 있습니다.

그러나 변경 가능한 객체를 갖는 것은 바람직하지 않습니다. 쿼리 결과 (실행하는 데 약 400ms 소요)를 텍스트 검색 엔진에 캐시하는 데 사용했으며 거의 ​​업데이트되지 않았습니다.


당신은 약한 참조가 아니라 소프트 참조에 대해 이야기하고 있습니다.
Pacerier
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.