WeakHashMap<K,V> 中的弱引用
相信很多人对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> 中的弱引用的更多相关文章
- 理解Java中的弱引用(Weak Reference)
本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...
- C# 中的弱引用 WeakReference
C#中的弱引用(WeakReference) 我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的.我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?.NET ...
- 简单说说.Net中的弱引用
弱引用是什么? 要搞清楚什么是弱引用,我们需要先知道强引用是什么.强引用并不是什么深奥的概念,其实我们平时所使用的.Net引用就是强引用.例如: Cat kitty = new Cat(); 变量ki ...
- Java中的弱引用
Strong references StringBuffer buffer = new StringBuffer(); 普通的对象创建都是这种类型,只要buffer还存在,对象就不会被GC回收.同时也 ...
- .NET中的弱引用
弱引用是什么? 要搞清楚什么是弱引用,我们需要先知道强引用是什么.强引用并不是什么深奥的概念,其实我们平时所使用的.Net引用就是强引用.例如: Cat cat = new Cat(); 变量cat就 ...
- 关于C#中的弱引用
本文前部分来自:http://www.cnblogs.com/mokey/archive/2011/11/24/2261605.html 分割线后为作者补充部分. 一:什么是弱引用 了解弱引用之前,先 ...
- Android开发过程中使用弱引用解决内存泄露的习惯
Java虽然有垃圾回收,但是仍然存在内存泄露,比如静态变量.缓存或其他长生命周期的对象引用了其他对象,这些被引用的对象就会长期不能被GC释放,导致内存泄露. 弱引用(WeakReference)是解决 ...
- C#中的弱引用(WeakReference)
我们平常用的都是对象的强引用,如果有强引用存在,GC是不会回收对象的.我们能不能同时保持对对象的引用,而又可以让GC需要的时候回收这个对象呢?.NET中提供了WeakReference来实现.弱引用可 ...
- Map<K, V> 中k,v如果为null就转换
Set<String> set = map.keySet(); if(set != null && !set.isEmpty()) { for(String key : s ...
随机推荐
- Wed Jul 04 18:01:38 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended
Wed Jul 04 18:01:38 CST 2018 WARN: Establishing SSL connection without server's identity verificatio ...
- 《算法》第三章部分程序 part 6
▶ 书中第三章部分程序,加上自己补充的代码,包含双向索引表.文建索引.稀疏向量类型 ● 双向索引表 package package01; import edu.princeton.cs.algs4.S ...
- How to CORS enable ArcGIS Server 10.2.1 to Access REST Services without Using proxy.ashx
http://gis.stackexchange.com/questions/86206/how-to-cors-enable-arcgis-server-10-2-1-to-access-rest- ...
- TP的di
依赖注入的意思是通过反射分析类所依赖的其他类,从容器中获取相应的对象并自动注入到类里面 首先依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合,鄙人学习了一下,看TP ...
- [转]让linux的coredump文件
原文标题:gdb结合coredump定位崩溃进程 原文:http://lazycat.is-programmer.com/posts/31925.html 这个文件中说的方法我试过了,在CentOS和 ...
- Linux中的Wheel组的作用
原文:http://www.360doc.com/content/11/0505/10/4644186_114496525.shtml Linux中的Wheel组的作用(用自己的话翻译的) (原文) ...
- 机器学习进阶-svm支持向量机
支持向量机需要解决的问题:找出一条最好的决策边界将两种类型的点进行分开 这个时候我们需要考虑一个问题,在找到一条直线将两种点分开时,是否具有其他的约束条件,这里我们在满足找到一条决策边界时,同时使得距 ...
- 本地项目 共享 到github仓库
一.安装git客户端 Window下安装Git客户端. 二.配置Intellij idea中的Git/ GitHub 选择Github,填写Host.Login和Password,然后Test是否成功 ...
- Android虚拟机与Java虚拟机 两种虚拟机的比较
在Android的体系框架中有一部分叫做Android Runtime,即Android运行时环境,这个环境包括了两个部分,一个是Android的核心类库,还有一个就是Dalvik虚拟机了. Andr ...
- Linux下查看与修改mtu值
MTU:通信术语 最大传输单元(Maximum Transmission Unit)是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位). 我们在使用互联网时进行的各种网络操作,都是通 ...