Java中的WeakHashMap指南
1.概述
在这篇文章中,我们将研究来自java.util 包的WeakHashMap。
为了理解这个数据结构,我们将在这里使用它来推出一个简单的缓存实现。然而,请记住,这只是为了理解地图是如何工作的,而创建自己的缓存实现几乎总是一个坏主意。
简单地说,WeakHashMap 是Map接口的基于哈希表的实现,其键是WeakReference 类型。
当WeakHashMap 的键不再正常使用时,WeakHashMap 中的条目将自动删除,这意味着没有单个 引用 指向那个键。当垃圾回收 (GC) 进程丢弃一个键时,它的条目实际上从映射中删除,因此此类的行为与其他 Map 实现有些不同。
2.强引用、软引用和弱引用
为了理解WeakHashMap如何工作,我们需要看一下WeakReference 类 – 这是WeakHashMap实现中键的基本构造。在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;
prime对象有一个指向它的强引用。
接下来,我们将prime 强引用包装成一个软引用。在使该强引用null之后,prime对象就有资格进入GC,但只有在JVM绝对需要内存时才会被收集。
2.3.弱引用
那些只被弱引用引用的对象会被急切地收集;在这种情况下,GC不会等到需要内存的时候才去收集。
我们可以通过以下方式在 Java 中创建一个WeakReference:
Integer prime = 1;
WeakReference<Integer> soft = new WeakReference<Integer>(prime);
prime = null;
当我们创建一个 prime 引用 null 时,prime 对象将在下一个 GC 周期中被垃圾收集,因为没有其他强引用指向给它。
WeakReference类型的引用被用作WeakHashMap中的键值。
3. 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。
4.总结
在这篇文章中,我们研究了Java中的引用类型,以充分了解java.util.WeakHashMap的工作原理。我们创建了一个简单的缓存,利用了WeakHashMap 的行为,并测试了它是否如我们预期的那样工作。
所有这些例子和代码片段的实现都可以在GitHub项目中找到--这是一个Maven项目,所以应该很容易导入和运行,因为它是一个Maven项目。