本文会经过对弱引用的定义讲起,而后经过案例的使用一步一步的深刻源码进行分析其原理,从而让读者深入的理解什么是弱引用,如何使用弱引用,什么场景下会使用弱引用,弱引用能够解决什么样的问题,以及它的源码实现是怎样的,其中会涉及的内存溢出,垃圾回收原理html
弱引用主要应用在不阻止它的key或者value 被回收的mapping。直接贴英文吧,翻译水平有限(weak references are for implementing canonicalizing mappings that do not prevent their keys (or values) from being reclaimed)java
弱引用的出现就是为了垃圾回收服务的。它引用一个对象,可是并不阻止该对象被回收。若是使用一个强引用的话,只要该引用存在,那么被引用的对象是不能被回收的。弱引用则没有这个问题。在垃圾回收器运行的时候,若是一个对象的全部引用都是弱引用的话,该对象会被回收程序员
理想的状况下,咱们但愿当咱们再也不使用一个对象的时候,可以在gc 发生的时候就把它回收掉。可是有些时候,因为咱们的粗忽,在坏的状况下会致使内存溢出。这种案例尤为发生在一个生命使用周期很长的map 存放了不少实际使用生命周期短的对象。请看下面这个例子面试
public class StrongRefenceDemo {
static Map<String, String> map;
public static void main(String[] args) throws Exception {
StrongRefenceDemo demo = new StrongRefenceDemo();
demo.strongTest();
System.out.println("gc 发生前:" + map.size());
System.out.println("开始通知GC");
//注意,这里只是经过垃圾回收器进行垃圾回收,并不必定立刻执行
System.gc();
Thread.sleep(1000 * 5);
System.out.println("gc 发生后:" + map.size());
}
/** * 强引用测试 */
public void strongTest() {
map = new HashMap<>();
String key = new String("key");
String value = new String("value");
map.put(key, value);
key = null;
value = null;
}
}
复制代码
运行后输出结果:小程序
gc 发生前:1
开始通知GC
gc 发生后:1
复制代码
从输出的结果能够看到,即便咱们经过把key和value 设置为null 来告诉jvm,咱们再也不使用这个对象了,map 里面对象依然没有被GC 回收,由于key和value 被一个强引用map 指向,根据可达性判断,垃圾回收器是不能回收掉key和value 这个对象的。map 被定义为statis 的静态变量,是一个使用生命周期很长的对象。在strongTest()方法中存在了一个key和value 的局部变量,它随着方法的执行完,这个变量的生命使用周期就结束了,可是粗糙的程序员忘记remove 了,这个时候垃圾回收器是不能回收它的。若是这种生命周期相对短的对象不少,最终就有可能消耗掉JVM中所有的内存。api
可是这里我有一个好奇,假如这里的key和value 指向的对象在执行完strongTest()方法 之后用不着了,可是我可能又不是很好的判断去主动调用remove 来移除它。想要垃圾回收器本身判断回收掉可不能够呢?答案实际上是能够的,这个时候就是弱引用上场了,请看下面程序数组
public class WeakRefenceDemo {
static Map<WeakReference<String>, String> map;
public static void main(String[] args) throws Exception {
WeakRefenceDemo demo = new WeakRefenceDemo();
demo.weakTest();
System.out.println("gc 发生前:" + map.size());
System.out.println("开始通知GC");
//注意,这里只是经过垃圾回收器进行垃圾回收,并不必定立刻执行
System.gc();
Thread.sleep(1000 * 5);
System.out.println("gc 发生后:" + map.size());
}
/** * 若引用测试 */
public void weakTest() {
map = new WeakHashMap<>();
String key = new String("key");
String value = new String("value");
map.put(new WeakReference<>(key), value);
key = null;
value = null;
}
}
复制代码
运行上面代码输出结果安全
gc 发生前:1
开始通知GC
gc 发生后:0
复制代码
从输出结果0,咱们能够判断已经成功被垃圾回收了。what?整个过程咱们只是把HashMap 换成了WeakHashMap,而且key 由String 换成了WeakReference。其实就是因为字符串只有弱引用指向,因此能够被垃圾回收掉。是否是很简单,若是到这里你就中止研究弱引用了,那就太暴殄天物了bash
上面的程序片断中,其实只有key 设置了为弱引用new WeakReference<>(key),那正常也就只有这个key 对应的内存被回收而已,因为没有调用remove ,里面的value 和entry 也是不会回收掉的,那为何最后输出的size 是0 呢? 很好的问题,咱们深刻去看WeakHashMap 的源码,咱们发现了一个神奇的方法expungeStaleEntries()。在看源码以前先解析下引用队列的概念: 在弱引用被回收的时候会把该对象放到引用队列中,也就意味着从引用队列中获取的对象都是被回收的对象,先解释到这里,足以知足咱们下面的源码分析了,接下来会作详细的解析数据结构
/** * Expunges stale entries from the table. */
private void expungeStaleEntries() {
//这里从引用队列里面取出一个已经被回收的对象
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;
//下面就是经过遍历链表来设置值为null 来告诉垃圾回收器回收掉
//注意WeakHashMap 和HashMap 的数据结构都是经过数组+链表组成的,只有理解了这点才知道下面的代码作了什么
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
//这里经过设置value 为null 来告诉垃圾回收
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
复制代码
从上面的代码片断,大概的意思就是从引用队列里面取出被回收的对象,而后和WeakHashMap 中的对象查找,找到以后就把对应的value 也设置为null,而且把对应的entry 设置为null,来告诉GC 去回收它。从源码能够看到expungeStaleEntries() 这个方法在执行WeakHashMap中的任何方法的时候都会被调用到的
/** * Returns the table after first expunging stale entries. */
private Entry<K,V>[] getTable() {
//被调用
expungeStaleEntries();
return table;
}
/** * Returns the number of key-value mappings in this map. * This result is a snapshot, and may not reflect unprocessed * entries that will be removed before next attempted access * because they are no longer referenced. */
public int size() {
if (size == 0)
return 0;
//被调用
expungeStaleEntries();
return size;
}
复制代码
到这里也就彻底明白为何value 不设置为弱引用和没有显性的调用remove 方法也能够回收掉了
从上面的的源码中,咱们大概知道了引用队列的使用,那为何要使用引用队列呢?假如没有引用队列,上面的例子咱们就须要遍历所有的元素一个一个的去找,若是数量少那还好,若是数量多的时候,确定就会出现一些性能问题。有了引用队列那就轻松能够解决上面的问题了。从WeakReference 源码中咱们能够看到有两个构造函数,第二个是须要传入引用队列的
public WeakReference(T referent) {
super(referent);
}
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
复制代码
Object referent = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference weakReference1 = new WeakReference<>(referent);
WeakReference weakReference2 = new WeakReference<>(referent, referenceQueue);
复制代码
使用中须要注意的细节: 因为弱引用的对象在GC 发生的时候均可能会被回收掉,因此在使用以前咱们都须要判断下是否为null 来避免空指针异常
Object referent3 = weakReference2.get();
if (referent3 != null) {
// GC hasn't removed the instance yet
} else {
// GC has cleared the instance
}
复制代码
若是你以为这篇内容对你挺有启发,我想邀请你帮我2个小忙: