Java四大引用
在介绍WeakHashMap前,我们先来介绍一下Java中的四大引用。
Java四大引用分别为:强引用、软引用、弱引用、虚引用。
它们的作用分别是:灵活的控制对象的生命周期提高对象的回收几率。
强引用
强引用:创建一个对象并把对象赋给一个引用变量。我们平时使用对象的方式就是强引用。
强引用有引用变量指向时永远不会被回收,JVM宁愿抛出OutOfMemory错误也不会回收强引用对象。
软引用
软引用:一般用于实现Java对象的缓存,可有可无。一般将有用但是非必须的对象用软引用进行关联。
软引用一般用SoftReference类。只要是软引用关联的对象,在Java发生内存溢出异常之前,会将这些对象列入要回收的范围。如果回收后,发现内存还是不够,才会抛出OutOfMemory异常。
作用:软引用可以用来实现内存敏感的告诉缓存,比如网页缓存、图片缓存等,使用软引用能防止内存泄漏。
举个例子:
首先我们设置JVM初始内存为3M,最大可用内存为5M。我们来看一看在限制了JVM内存的情况下,下面代码能否正常运行:
public class SofetReferenceTest {
public static void main(String[] args) {
byte[] buffer = new byte[1024*1024*3];
}
}
我们创建了一个大小为3M的数组,可以正常运行。
此时,我们修改数组的大小为5M。看看能否正常运行。
byte[] buffer = new byte[1024*1024*5];
我们可以看到,抛出了OutOfMemory异常。这是由于,我们是以强引用的形式创建的数组,即使JVM内存不够,强引用也不会被JVM回收。所以会抛出OutOfMemory。
接下来我们看看软引用有什么不同。我们创建了10个大小为1M的数组,并用SoftReference关联。然后调用垃圾只回收机制
public static void main(String[] args) {
ArrayList<Object> arrayList = new ArrayList<>();
byte[] buffer = null;
for (int i = 0; i < 10; i++) {
buffer= new byte[1024*1024];
SoftReference<byte[]> softReference = new SoftReference<>(buffer);
arrayList.add(softReference);
}
System.gc();
for (int i = 0; i < 10; i++) {
Object o = ((SoftReference) arrayList.get(i)).get();
System.out.println(o);
}
}
我们可以看到,软引用关联的对象在内存不够的情况下,会被JVM回收。
如果一个对象剩下的唯一一个引用为软引用时,则这个对象是软可及的。
作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性外,也具有Java对象的一般性。所以当软可及对象被回收后。虽然这个SoftReference对象的get()返回null,这个SoftReference对象已经不再具有价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。所以提供了ReferenceQueue类。如果在创建SofetReference对象的时候,使用一个ReferenceQueue对象作为参数提供给SoftReference的构造方法时:
ReferenceQueue queue = new ReferenceQueue();
SoftReference ref=new SoftReference(aMyObject, queue);
当这个SoftReference所创建的软引用被垃圾回收机制回收的同时,JVM会将这个软引用加入到与之关联的引用队列中。在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。一个软引用对象被回收的同时,会被放入ReferenceQueue队列中。所以我们可以判断ReferenceQueue是否为空,来检查哪个SoftReference对象已经被回收了。
弱引用
弱引用,用来描述非必须对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。使用WeakReferece类来管理弱引用对象。
public static void main(String[] args) {
// 一个强引用对象
Person p1 = new Person(1,"王五");
WeakReference w = new WeakReference(p1);
System.out.println(w.get());//Person{id=1, name='王五'}
p1 = null; // 取消强引用指向
System.gc();
System.out.println(w.get());//null
}
只要JVM进行垃圾回收,被弱引用关联的对象必定会被回收掉。不过要注意的是,这里所说的被弱引用关联的对象指仅仅被弱引用关联,如果还存在其他强引用同时与之关联,则垃圾回收时不会回收该对象(软引用也是如此)。
虚引用
使用PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。虚引用一般用来检测对象是否被回收。
虚引用必须和引用队列关联使用。垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
WeakHashMap
概述
WeakHashMap中的键相比于HashMap不同。WeakHashMap中的键是"弱键",在WeakHashMap中,当某个键不再正常使用时,会被WeakHashMap中自动移除。
"弱键"是通过WeakReference和ReferenceQueue实现的。
当某"弱键"不再被其他对象引用,并被gc回收时,在gc回收该键的同时,这个弱键会被添加到ReferenceQueue(queue)中。当下一次,我们需要操作WeakHashMap时,会首先同步table和queue。table中保存了全部的键值对,queue中保存了已经被gc回收的键值对,同步它们就是删除table中被gc回收的键值对。
expungeStaleEntries()
expungeStaleEntries()内部实现了移除其内部不用的entry从而达到自动释放内存的目的。我们每访问依次WeakHashMap的时候,都会调用该函数将table中已经被回收的键值对清理一遍。
private void expungeStaleEntries() {
// 遍历每一个被回收的entyry。
for (Object x; (x = queue.poll()) != null; ) {
// 加锁删除
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
// 计算在table中位置。
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;
}
}
}
}