WeakHashMap 继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。
和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap
以上为WeakHashMap的基本介绍,不过多叙说,这里说一个问题,当key置位null的时候,需要在gc进行一次垃圾回收之后,再回把对应的key,value都回收掉,这里回收有一个时差,会出现明明这个key和map已经不存在于map中了,但是通过mao.size()获取大小的时候,依旧会统计上。
import java.util.HashMap;
import java.util.WeakHashMap;
public class WeakReferenceDemo {
public static void main(String[] args) {
weakMap();
}
private static void weakMap() {
WeakHashMap hashMap = new WeakHashMap<>();
String value = "WeakHashMao";
Integer key = new Integer(1);
hashMap.put(key, value);
System.out.println(hashMap);
key = null; //将key置位null
System.out.println(hashMap);
System.gc(); //这里模拟一次GC过程
System.out.println(hashMap + "\t " + hashMap.size());
}
}
运行结果预计的是
{1=WeakHashMao}
{1=WeakHashMao}
{} 0
但是实际的确实:
//结果一
{1=WeakHashMao}
{1=WeakHashMao}
{} 1
//结果二
{1=WeakHashMao}
{1=WeakHashMao}
{} 0
交替出现。
分析出现的原因,可能是gc执行过程。加上 -XX:+PrintGCDetails 看一下gc的日志,
{1=WeakHashMao}
{1=WeakHashMao}
[GC (System.gc()) [PSYoungGen: 2663K->800K(38400K)] 2663K->808K(125952K), 0.0052766 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 800K->0K(38400K)] [ParOldGen: 8K->642K(87552K)] 808K->642K(125952K), [Metaspace: 3150K->3150K(1056768K)], 0.0135799 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
{} 1
{1=WeakHashMao}
{1=WeakHashMao}
[GC (System.gc()) [PSYoungGen: 2663K->704K(38400K)] 2663K->712K(125952K), 0.0014530 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 704K->0K(38400K)] [ParOldGen: 8K->617K(87552K)] 712K->617K(125952K), [Metaspace: 3130K->3130K(1056768K)], 0.0054741 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
{} 0
通过两次gc看出来,第一次新生代的额gc,堆区的内存从2663k - > 704k,第二次gc,清理新生代的垃圾 704 全部清空了,老年代的内存总内存是 8K + 704K = 712K, 最后变为 617K 。
从这里可以看出来,在堆内存new的对象,在经历 full gc的时候,堆内存的对象,进入了老年代。
从垃圾回收这边没看出问题。
那么接下来看看线程执行情况
main线程的已经执行完毕了,但是调用的map.size方法还在执行,由此可以得出以下几个原因:
1,异步调用,但是代码中没有异步的指令。 所以排除
2,指令重排序,重排的地方是这里
先执行了return size;
然后在执行
expungeStaleEntries();
个人见解,如有其他意见,请指点。
参考文档:
https://blog.csdn.net/qiuhao9527/article/details/80775524