概述
WeakHashMap 常用于缓存,当发生GC的时候,整个Map会被清空。
public static void main(String[] args) throws InterruptedException {
String key1 = new String("k1");
String key2 = new String("K2");
Map<String, String> wmap = new WeakHashMap<String, String>();
wmap.put(key1, "wval1");
wmap.put(key2, "wval2");
key2 = null;
key1 = null;
System.gc();
System.out.println("first access: " + wmap.get("k1"));
System.out.println("second access: " + wmap.get("k2"));
}
first access: null
second access: null
我们来看看WeakHashMap的源码
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V> {
Entry<K,V>[] table;
# 容器中存放的Node继承与弱引用
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
# key 是弱引用
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
}
}
从源码中可以发现WeakHashMap里面存放的Entry是继承与 WeakReference,所以我们得先看看WeakReference的用法。
WeakReference 和 ReferenceQueue
首先先说结论,然后再来推导。 如果 WeakReference 注册到了 ReferenceQueue 中,当WeakReference里面引用的对象被GC了,这个Queue会收到通知。
public static class WeakPerson extends WeakReference<Object>{
private String name;
@Override
protected void finalize() throws Throwable {
System.out.println("weakkperson 结束");
}
public WeakPerson(Object referent, ReferenceQueue<Object> q, String name) {
super(referent, q);
this.name = name;
}
}
public static void main(String[] args) throws Exception{
String wife = new String("jerry");
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakPerson peter = new WeakPerson(wife, queue, "peter");
new Thread(()->{
while (true){
WeakPerson weakPerson = (WeakPerson)queue.poll();
if (weakPerson == null){
continue;
}
System.out.println(weakPerson.name+"---容器还存在");
}
}).start();
wife = null;
System.out.println("gc拉");
System.gc();
System.in.read();
}
Queue是如何能找到对象被销毁的
JVM在GC时如果当前对象只被Reference对象引用
,JVM会根据Reference具体类型与堆内存的使用情况决定是否把对应的Reference对象加入到一个由Reference构成的pending链表上
,如果能加入pending链表JVM同时会通知ReferenceHandler线程进行处理。该线程就会把Reference放到队列中,Queue就找到对象了
Reference 的四个状态
- Active 新建状态,刚刚new出来的 Reference就是这个状态
- Pending 如果Reference 引用的数据被销毁了,并且自身还注册到了一个Queue中,就会变成 Pending状态,准备入
自己注册的Queue
- Enqueued 已经在自己的队列中了
源码阅读
Reference 里面很多变量都跟状态有关,状态不同,指向的位置不同,都是由JVM来控制的
public abstract class Reference<T> {
# Reference 引用的对象
private T referent; /* Treated specially by GC */
# 注册的队列
volatile ReferenceQueue<? super T> queue;
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
# next指针,根据状态不同指向不同
volatile Reference next;
/* When active: next element in a discovered reference list maintained by GC (or this if last)
* pending: next element in the pending list (or null if last)
* otherwise: NULL
*/
transient private Reference<T> discovered; /* used by VM */
/* List of References waiting to be enqueued. The collector adds
* References to this list, while the Reference-handler thread removes
* them. This list is protected by the above lock object. The
* list uses the discovered field to link its elements.
*/
private static Reference<Object> pending = null;
# 这个线程会在 Reference 是pending的时候被调用
private static class ReferenceHandler extends Thread {
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
# 让pending的 Reference放到注册的队列中
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
# 这就是入队列的操作
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
static {
# 开启线程去监听有没有 Reference 变成 pending
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();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
}
ReferenceQueue 源码阅读。没什么特别的,就是一个单项链表,然后取的时候加了个锁
public class ReferenceQueue<T> {
static ReferenceQueue<Object> NULL = new Null<>();
static ReferenceQueue<Object> ENQUEUED = new Null<>();
static private class Lock { };
private Lock lock = new Lock();
private volatile Reference<? extends T> head = null;
private long queueLength = 0;
# 入队列的操作
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
synchronized (lock) {
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
if ((queue == NULL) || (queue == ENQUEUED)) {
return false;
}
assert queue == this;
r.queue = ENQUEUED;
r.next = (head == null) ? r : head;
head = r;
queueLength++;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(1);
}
lock.notifyAll();
return true;
}
}
# 就是一个简单的从队列中找数据如果没有就返回null的操作
private Reference<? extends T> reallyPoll() { /* Must hold lock */
Reference<? extends T> r = head;
if (r != null) {
@SuppressWarnings("unchecked")
Reference<? extends T> rn = r.next;
head = (rn == r) ? null : rn;
r.queue = NULL;
r.next = r;
queueLength--;
if (r instanceof FinalReference) {
sun.misc.VM.addFinalRefCount(-1);
}
return r;
}
return null;
}
public Reference<? extends T> poll() {
if (head == null)
return null;
synchronized (lock) {
return reallyPoll();
}
}
}
回到WeakHashMap
通过了学习 WeakReference 的相关知识,发现 WeakHashMap 里面的 Node的key如果销毁了会触发一个Queue的监听事件,我们来看看源码. 一次 get请求会把所有的已经销毁的key对应的value也置空.等下次gc对应的value就被清理了
# 获得table之前都会调用 expungeStaleEntries 方法
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
private void expungeStaleEntries() {
# 监听 queue,一次调用会把 queue中所有的数据清除.
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, 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;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}