Java中WeakHashMap的回收问题详解
作者:幸平xp
一、四大引用
在介绍WeakHashMap回收问题之前,我们要先介绍四大引用类型:
- 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回 收掉被引用的对象。
- 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。
- 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。
- 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供 了PhantomReference类来实现虚引用。
对于本文主要涉及的为强引用,弱引用。其他引用本篇就不再多提了。 总结一下强引用与弱引用:
- 强引用:主要为new出来的对象,就算内存满了,也不会回收它。因此当强引用过多时会导致OOM(内存溢出),HashMap中key、value关系就为强引用。
- 弱引用:被弱引用关联的对象只 能生存到下一次垃圾收集发生为止,而WeakHashMap中的key,value就是弱引用关系。
二、WeakHashMap的回收问题
首先基本大概讲述一下WeakHashMap弱键的原理:
大致上是通过WeakReference和ReferenceQueue实现的。WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。
实现步骤是:
1、新建WeakHashMap,将“键值对”添加到WeakHashMap中。实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
2、当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
3、 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap。
读者看到这可能还是比较抽象。没关系,接下来我们直接上一些例子:
public static void main(String[] args) { WeakHashMap<String,String> weakMap = new WeakHashMap<>(); weakMap.put("k1","v1"); String k2 = new String("k2"); weakMap.put(k2,"v2"); weakMap.put(new String("k3"),"k3"); System.out.println("weakMap回收前打印结果:"); System.out.println(weakMap); // 因为WeakHashMap内部维护的是一个弱引用映射所以gc直接回收 System.gc(); System.out.println("weakMap回收后打印结果:"); System.out.println(weakMap); }
以上的打印结果是什么呢?
可以看到打印结果是回收后的结果是只有k3的key-value被回收掉了。那么为什么呢?
这得首先从创建一个String对象说起,创建一个String对象最常见的有两种情况:
// 第一种情况 String str1 = "a"; // 第二种情况 String str2 = new String("b");
对于第一种情况其对象的创建会先在字符串常量池判断有没有对应的字符串常量,如果没有创建一个字符串常量一,然后将其引用指向在字符串常量池中的那个常量,如果存在则不需创建直接返回引用。 对于第二种情况其对象的引用还是会先在字符串常量池判断有没有对应的字符串常量,如果没有创建一个字符串常量。 而后在堆中拷贝一个其字符串常量的对象,最后返回堆上的引用。
我画了张图对于情况二来讲应该是这个:
那么对于对象创建的引用我们了解了,那么接下来回到例子就很好理解。
- 首先是 weakMap.put(“k1”,“v1”); 很明显这里面是k1的引用是放在字符串常量池里的,是一种强引用。因此关于k1的key会一直保持下去。所以就是强引用,因此不会被gc。
然后则是:
String k2 = new String("k2"); weakMap.put(k2,"v2");
这里面的很明显k2为new String的WeakHashMap外部的强引用,因此k2的指针其实是在WeakHashMap外部被引用,所以不会被gc。如果要回收它也很容易,则只需要将其外部的引用k2置为null:
String k2 = new String("k2"); weakMap.put(k2,"v2"); k2 = null;
而相对直接在WeakHashMap内直接创建对象返回引用的话,那么很明显就是弱键了。
weakMap.put(new String("k3"),"k3");
那么如何可以如何才能不回收这个字符串对象呢?我们刚刚画图提到了,我们可以用intern()方法返回其字符串常量池对应的对象。
weakMap.put(new String("k3").intern(),"k3");
这个时候小伙伴可以自己去试一下,此时这个对象gc后也不会被回收了。就如同情况1:weakMap.put(“k1”,“v1”);
最后的最后附上完整代码,有兴趣的可以自己去试试:
public class MapGC { public static void main(String[] args) { WeakHashMap<String,String> weakMap = new WeakHashMap<>(); weakMap.put("k1","v1"); String k2 = new String("k2"); weakMap.put(k2,"v2"); weakMap.put(new String("k3"),"k3"); System.out.println("weakMap回收前打印结果:"); System.out.println(weakMap); // 因为WeakHashMap内部维护的是一个弱引用映射所以gc直接回收 System.gc(); System.out.println("weakMap第一次回收后打印结果:"); System.out.println(weakMap); k2 = null; weakMap.put(new String("k3").intern(),"k3"); System.gc(); System.out.println("weakMap第二次回收后打印结果:"); System.out.println(weakMap); } }
输出结果:
到此这篇关于Java中WeakHashMap的回收问题详解的文章就介绍到这了,更多相关WeakHashMap回收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!