相信很多人对WeakHashMap并没有完全理解。

WeakHashMap 持有的弱引用的 Key。

1. 弱引用的概念:

  弱引用是用来描述非必需对象的,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

2. WeakHashMap中的弱引用

Key是如何被清除的?

  WeakHashMap中的清除Key的核心方法:

private void expungeStaleEntries() {
Entry<K,V> e;
while ( (e = (Entry<K,V>) queue.poll()) != null) {
int h = e.hash;
int i = indexFor(h, table.length); Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
e.next = null; // Help GC
e.value = null; // " "
size--;
break;
}
prev = p;
p = next;
}
}
}

  查看调用关系,可以看到几乎所有的方法都直接或间接的调用了该方法。但是查看WeakHashMap源码,并没有找到何时将Entry放入queue中。

  那么queue队列中的Entry是如何来的?

Key弱引用是如何关联的?

  毫无疑问,一定是在put元素的时候,key被设置为弱引用。

    public V put(K key, V value) {
K k = (K) maskNull(key);
int h = HashMap.hash(k.hashCode());
Entry[] tab = getTable();
int i = indexFor(h, tab.length); for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<K,V>(k, value, queue, h, e); //创建一个新节点
if (++size >= threshold)
resize(tab.length * 2);
return null;
}

  其中的queue为:

    /**
* Reference queue for cleared WeakEntries
*/
private final ReferenceQueue<K> queue = new ReferenceQueue<K>();

再来看一下Entry<K,V>的声明及构造函数:

    private static class Entry<K,V> extends WeakReference<K> implements Map.Entry<K,V> {/**
* Creates new entry.
*/
Entry(K key, V value,
ReferenceQueue<K> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
}

  Entry<K,V>继承了WeakReference,并且在构造函数中将key 和 queue 提交给WeakReference,那么再来看一下WeakReference的构造函数:

public class WeakReference<T> extends Reference<T> {
  //....
public WeakReference(T referent, ReferenceQueue<? super T> q) {
  super(referent, q);
}
}
public abstract class Reference<T> {

  private static Reference pending = null;
  Reference(T referent, ReferenceQueue<? super T> queue) {
  this.referent = referent;
  this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
  }
}

  现在答案就在Reference中。

打开Reference源码,可以看到一个静态块:

  static {
  ThreadGroup tg = Thread.currentThread().getThreadGroup();
  for (ThreadGroup tgn = tg;tgn != null;tg = tgn, tgn = tg.getParent());
  Thread handler = new ReferenceHandler(tg, "Reference Handler");
  /* If there were a special system-only priority greater than
  * MAX_PRIORITY, it would be used here
  */
   handler.setPriority(Thread.MAX_PRIORITY);
   handler.setDaemon(true);
   handler.start();
}

  其中for循环直到 获取到 JVM 线程组,使用JVM线程执行ReferenceHandler。

  ReferenceHandler是Reference的内部类:

 private static class ReferenceHandler extends Thread {

    ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
} public void run() {
for (;;) {
   Reference r;
  synchronized (lock) {
  if (pending != null) {
    r = pending;
    Reference rn = r.next;
    pending = (rn == r) ? null : rn;
    r.next = r;
  } else {
    try {
    lock.wait();
    } catch (InterruptedException x) { }
    continue;
  }
  }    // Fast path for cleaners
  if (r instanceof Cleaner) {
  ((Cleaner)r).clean();
  continue;
   }    ReferenceQueue q = r.queue;
   if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}

  现在我们应该已经清楚了,守护线程一直执行入队操作,将key关联的Entry<K,V>放入queue中。

  但是将key放入queue中需要前提条件: pending

  这个pending是在垃圾回收的时候,JVM计算对象key的可达性后,发现没有该key对象的引用,那么就会把该对象关联的Entry<K,V>添加到pending中,

所以每次垃圾回收时发现弱引用对象没有被引用时,就会将该对象放入待清除队列中,最后由应用程序来完成清除,WeakHashMap中就负责由

方法expungeStaleEntries()来完成清除。

例子: 

   @Test
public void weakHashMap(){
Map<String, String> weak = new WeakHashMap<String, String>();
weak.put(new String("1"), "1");
weak.put(new String("2"), "2");
weak.put(new String("3"), "3");
weak.put(new String("4"), "4");
weak.put(new String("5"), "5");
weak.put(new String("6"), "6");
System.out.println(weak.size());
System.gc(); //手动触发 Full GC
try {
Thread.sleep(50); //我的测试中发现必须sleep一下才能看到不一样的结果
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(weak.size());
}

  上面的例子是可以正确的,但是下面的就有问题了:

   @Test
public void weakHashMap(){
Map<String, String> weak = new WeakHashMap<String, String>();
weak.put("1", "1");
weak.put("2", "2");
weak.put("3", "3");
weak.put("4", "4");
weak.put("5", "5");
weak.put("6", "6");
System.out.println(weak.size());
System.gc();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(weak.size());
}

  无论sleep多长时间,引用也不会被清除。这涉及到String在JVM中的工作方式了,这个问题留给读者自己思考。

WeakHashMap<K,V> 中的弱引用的更多相关文章

  1. 理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  2. C# 中的弱引用 WeakReference

    C#中的弱引用(WeakReference)   我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的.我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?.NET ...

  3. 简单说说.Net中的弱引用

    弱引用是什么? 要搞清楚什么是弱引用,我们需要先知道强引用是什么.强引用并不是什么深奥的概念,其实我们平时所使用的.Net引用就是强引用.例如: Cat kitty = new Cat(); 变量ki ...

  4. Java中的弱引用

    Strong references StringBuffer buffer = new StringBuffer(); 普通的对象创建都是这种类型,只要buffer还存在,对象就不会被GC回收.同时也 ...

  5. .NET中的弱引用

    弱引用是什么? 要搞清楚什么是弱引用,我们需要先知道强引用是什么.强引用并不是什么深奥的概念,其实我们平时所使用的.Net引用就是强引用.例如: Cat cat = new Cat(); 变量cat就 ...

  6. 关于C#中的弱引用

    本文前部分来自:http://www.cnblogs.com/mokey/archive/2011/11/24/2261605.html 分割线后为作者补充部分. 一:什么是弱引用 了解弱引用之前,先 ...

  7. Android开发过程中使用弱引用解决内存泄露的习惯

    Java虽然有垃圾回收,但是仍然存在内存泄露,比如静态变量.缓存或其他长生命周期的对象引用了其他对象,这些被引用的对象就会长期不能被GC释放,导致内存泄露. 弱引用(WeakReference)是解决 ...

  8. C#中的弱引用(WeakReference)

    我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的.我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?.NET中提供了WeakReference来实现.弱引用可 ...

  9. Map<K, V> 中k,v如果为null就转换

    Set<String> set = map.keySet(); if(set != null && !set.isEmpty()) { for(String key : s ...

随机推荐

  1. redis 连接超时。。

    在虚拟机安装后.在项目中加入redis 想测试一下功能 然后在物理机死活连不上. 1.用物理机ping 虚拟机的IP 通.... 2.然后.把redis.conf 里面bind 0.0.0.0 依然没 ...

  2. NEU(Fst Network Embedding Enhancement via High Order Proximity Approximation)

    NEU(Fst Network Embedding Enhancement via High Order Proximity Approximation) NEU:通过对高阶相似性的近似,加持快速网络 ...

  3. 【Excel】绘图案例_常见复合图:簇状图+堆积图+折线图

    前言 最近有朋友让我帮忙用excel画图,老实说我很讨厌用excel画图,点来点去,复杂一些还不能复用,非常繁琐.当然,入门也很简单.需求时不同城市.不同产品的2016和2017销量及环比数据,这应该 ...

  4. 17. 判断是否IE浏览器

    var isNotIE = !window.VBArray;    //判断是否IE浏览器    if(isNotIE) {        //如果不是IE浏览器        $("#da ...

  5. Node.js之process模块

    注意⚠️:process为nodejs内置对象,不需要实例化,改模块用来与当前进程进行互动,可以通过全局变量process访问,它是一个EventEmitter对象的实例. process对象提供一系 ...

  6. scrapy工作原理概述

    当运行scrapy crawl spider 时,会生成一个crawl命令对象,scrapy是调用execute函数(cmdlin.py)来执行命令的,execute函数会给命令对象添加crawler ...

  7. DDD随笔-Axon

    1. 命令处理程序从存储库中检索域对象(聚合)并执行它们的方法来更改它们的状态.这些聚合通常包含实际的业务逻辑,因此负责维护自己的状态.聚合的状态变化导致产生领域事件.领域事件和聚合形成领域模型. 2 ...

  8. Shell 编程(函数)

    声明函数 demoFun(){ echo "这是我的第一个 shell 函数!" } 函数名(){ ...函数体 } 在Shell中,调用函数时可以向其传递参数.在函数体内部,通过 ...

  9. react-native Animated, 旋转动画

    Animated 仅封装了四个可以动画化的组件: View.Text.Image.ScrollView 可以使用 Animated.createAnimatedComponent()来封装你自己的组件 ...

  10. Spring Cloud (5)hystrix 服务降级

    一.是什么 二. 2.1 2.2 2.3 2.4 2.5 yml