概述
在这篇文章中,我们将探索 java.util 包中的WeakHashMap
。
为了理解数据结构,我们将在这里使用它来推出一个简单的缓存实现。但是,请记住,这是为了了解地图的工作原理,并且创建自己的缓存实现几乎总是一个坏主意。
简而言之,WeakHashMap
是Map接口的基于哈希表的实现,其键为WeakReference
类型。
当WeakHashMap中的某个条目不再被普通使用时,该条目将被自动删除,这意味着没有单个Reference 指向该键。当垃圾回收(GC)进程丢弃某个键时,其条目将有效地从映射中删除,因此此类的行为与其他Map实现有所不同。
Strong, Soft, and Weak References
要了解WeakHashMap的工作原理,我们需要查看WeakReference类 -这是WeakHashMap实现中键的基本构造。在Java中,我们有三种主要的引用类型,我们将在以下各节中进行解释。
强引用
强引用是我们在日常编程中使用的最常见的引用类型:
Integer prime = 1;
变量prime具有对值为1 的Integer对象的强引用。任何具有指向其的强引用的对象均不适合使用GC。
弱引用
简而言之,只有在JVM绝对需要内存之前,才会对具有SoftReference 指向该对象的对象进行垃圾回收。
让我们看看如何在Java中创建SoftReference:
Integer prime = 1;
SoftReference<Integer> soft = new SoftReference<Integer>(prime);
prime = null;
该主要对象有指向它很强的借鉴意义。
接下来,我们将主要的强引用包装为软引用。将强引用设为null之后,素对象可以使用GC,但仅在JVM绝对需要内存时才收集该对象。
虚引用
仅弱引用所引用的对象会被紧急收集;在这种情况下,GC不会等到需要内存。
我们可以通过以下方式在Java中创建WeakReference:
Integer prime = 1;
WeakReference<Integer> soft = new WeakReference<Integer>(prime);
prime = null;
当我们将主要引用设为null时,主要对象将在下一个GC周期中被垃圾回收,因为没有其他强引用指向该对象。
WeakReference类型的引用在WeakHashMap中用作键。
WeakHashMap作为高效的内存缓存
假设我们要建立一个缓存,将大图像对象保留为值,并将图像名称保留为键。我们想选择一个合适的地图实现来解决该问题。
使用简单的HashMap将不是一个好的选择,因为值对象可能会占用大量内存。而且,即使它们不再在我们的应用程序中使用,它们也永远不会被GC进程从缓存中回收。
理想情况下,我们需要一个Map实现,该实现允许GC自动删除未使用的对象。当我们的应用程序中任何地方都没有使用大图像对象的键时,该条目将从内存中删除。
幸运的是,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);
我们正在创建一个WeakHashMap实例,该实例将存储我们的BigImage对象。我们将BigImage对象作为值,并将imageName对象引用作为键。的imageName将被存储在一个图作为WeakReference的类型。
接下来,我们将imageName引用设置为null,因此不再有指向bigImage对象的引用。WeakHashMap的默认行为是在下一个GC上回收没有引用的条目,因此该条目将在下一个GC进程中从内存中删除。
我们正在调用System.gc()来强制JVM触发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。