1. 개요

이 기사에서는 java.util 패키지 의 WeakHashMap살펴보겠습니다 .

데이터 구조를 이해하기 위해 여기에서 간단한 캐시 구현을 롤아웃하는 데 사용할 것입니다. 그러나 이것은 Map가 작동하는 방식을 이해하기 위한 것이며 자체 캐시 구현을 만드는 것은 거의 항상 나쁜 생각이라는 점을 명심하십시오.

간단히 말해 WeakHashMap 은 WeakReference 유형 의 키를 사용하는 Map 인터페이스 의 해시 테이블 기반 구현입니다 .

WeakHashMap 의 항목은 키가 더 이상 일반적으로 사용되지 않을 때 자동으로 제거됩니다. 즉, 해당 키를 가리키는 단일 참조 가 없습니다 . 가비지 수집(GC) 프로세스가 키를 폐기하면 해당 항목이 맵에서 효과적으로 제거되므로 이 클래스는 다른 맵 구현 과 다소 다르게 동작 합니다.

2. 강한 참조, 부드러운 참조, 약한 참조

WeakHashMap 의 작동 방식을 이해하려면 WeakHashMap 구현 의 키에 대한 기본 구조인 WeakReference 클래스 를 살펴봐야 합니다 . Java에는 다음 섹션에서 설명할 세 가지 주요 유형의 참조가 있습니다.

2.1. 강력한 참조

강한 참조는 일상적인 프로그래밍에서 사용 하는 가장 일반적인 유형의 참조 입니다.

Integer prime = 1;

변수 prime 에는 값이 1 Integer 개체에 대한 강력한 참조가 있습니다. 이를 가리키는 강력한 참조가 있는 개체는 GC에 적합하지 않습니다 .

2.2. 소프트 참조

간단히 말해, SoftReference 가 가리키는 객체는 JVM이 메모리를 절대적으로 필요로 할 때까지 가비지 수집되지 않습니다.

Java 에서 SoftReference 를 생성하는 방법을 살펴보겠습니다 .

Integer prime = 1;  
SoftReference<Integer> soft = new SoftReference<Integer>(prime); 
prime = null;

기본 개체에는 이를 가리키는 강력한 참조가 있습니다 .

다음으로 프라임 강한 참조를 소프트 참조로 래핑합니다. 강력한 참조를 null 로 만든 후 주요 개체는 GC에 적합하지만 JVM에 메모리가 절대적으로 필요한 경우에만 수집됩니다.

2.3. 약한 참조

약한 참조에 의해서만 참조되는 개체는 열심히 가비지 수집됩니다. 이 경우 GC는 메모리가 필요할 때까지 기다리지 않습니다.

다음과 같은 방법으로 Java에서 WeakReference 를 만들 수 있습니다 .

Integer prime = 1;  
WeakReference<Integer> soft = new WeakReference<Integer>(prime); 
prime = null;

기본 참조 를 null만들면 기본 개체를 가리키는 다른 강력한 참조가 없으므로 다음 GC 주기에서 기본 개체가 가비지 수집됩니다 .

WeakReference 유형 의 참조는 WeakHashMap 에서 키로 사용됩니다 .

3. 효율적인 메모리 캐시로서의 WeakHashMap

큰 이미지 개체를 값으로 유지하고 이미지 이름을 키로 유지하는 캐시를 만들고 싶다고 가정해 보겠습니다. 우리는 그 문제를 해결하기 위해 적절한 Map 구현을 선택하고 싶습니다.

값 개체가 많은 메모리를 차지할 수 있으므로 간단한 HashMap 을 사용하는 것은 좋은 선택이 아닙니다. 또한 애플리케이션에서 더 이상 사용하지 않는 경우에도 GC 프로세스에 의해 캐시에서 회수되지 않습니다.

이상적으로 는 GC가 사용하지 않는 개체를 자동으로 삭제할 수 있는 Map 구현이 필요합니다. 큰 이미지 객체의 키가 애플리케이션에서 사용되지 않는 경우 해당 항목은 메모리에서 삭제됩니다.

다행스럽게도 WeakHashMap 에는 정확히 이러한 특성이 있습니다. WeakHashMap 을 테스트하고 어떻게 작동하는지 살펴보겠습니다.

WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImage = new BigImage("image_id");
UniqueImageName imageName = new UniqueImageName("name_of_big_image");

map.put(imageName, bigImage);
assertTrue(map.containsKey(imageName));

imageName = null;
System.gc();

await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);

BigImage 개체 를 저장할 WeakHashMap 인스턴스를 만들고 있습니다. BigImage 객체를 값으로, imageName 객체 참조를 키로 넣습니다 . imageNameMap에 WeakReference 유형으로 저장됩니다.

다음으로 imageName 참조를 null 로 설정하므로 bigImage 개체 를 가리키는 참조가 더 이상 없습니다 . WeakHashMap 의 기본 동작은 다음 GC에서 참조가 없는 항목을 회수하는 것이므로 이 항목은 다음 GC 프로세스에 의해 메모리에서 삭제됩니다.

JVM이 GC 프로세스를 트리거하도록 강제하기 위해 System.gc() 를 호출하고 있습니다. GC 주기 후 WeakHashMap 은 비어 있습니다.

WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImageFirst = new BigImage("foo");
UniqueImageName imageNameFirst = new UniqueImageName("name_of_big_image");

BigImage bigImageSecond = new BigImage("foo_2");
UniqueImageName imageNameSecond = new UniqueImageName("name_of_big_image_2");

map.put(imageNameFirst, bigImageFirst);
map.put(imageNameSecond, bigImageSecond);
 
assertTrue(map.containsKey(imageNameFirst));
assertTrue(map.containsKey(imageNameSecond));

imageNameFirst = null;
System.gc();

await().atMost(10, TimeUnit.SECONDS)
  .until(() -> map.size() == 1);
await().atMost(10, TimeUnit.SECONDS)
  .until(() -> map.containsKey(imageNameSecond));

imageNameFirst 참조 null 로 설정됩니다 . imageNameSecond 참조는 변경되지 않은 상태로 유지됩니다 . GC가 트리거된 후 맵에는 하나의 항목( imageNameSecond )만 포함됩니다 .

4. 결론

이 기사에서는 java.util 이 어떻게 작동하는지 완전히 이해하기 위해 Java의 참조 유형을 살펴보았습니다 . WeakHashMap 이 작동합니다. WeakHashMap 의 동작을 활용 하고 예상대로 작동하는지 테스트 하는 간단한 캐시를 만들었습니다 .

이 모든 예제와 코드 스니펫의 구현은 Maven 프로젝트인 GitHub 프로젝트 에서 찾을 수 있으므로 그대로 가져오고 실행하기 쉬워야 합니다.

Generic footer banner