Java中的软(弱)引用

Java中的强、软、弱、虚引用

在JDK中我们可以看到有一个java.lang.ref的包,这个包中就是Java中实现强、软、弱、虚引用的包,如下:

Java中的软引、弱、虚引用

PhantomReference

虚引用:如果一个对象持有虚引用,就和没有持有引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收的活动,虚引用还有一个和弱、软引用不动的地方是虚引用必须和引用队列联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象内存前把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列是否已经添加了虚引用,来知道对象是否将要被回收,这样我们就可以在对象被回收之前进行一些操作。

Reference

这个是引用的基类,是一个抽象类,封装了关于引用的相关操作,如:去除引用、比较、获取引用对象、判断引用地址是否相同。

ReferenceQueue

引用队列:在垃圾回收器回收对象之前,会将对应关联的引用添加到该队列。

SoftReference

软引用:如果内存空间不够用时,垃圾回收器就会回收该引用所引用到的内存对象。使用软引用可以实现内存敏感的高速缓存,软引用可以和引用队列联合使用。

WeakReference

弱引用:当垃圾回收器发现某个对象只有弱引用时无论是否内存够用都会回收该对象,不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现只持有弱引用的对象,弱引用也可以和引用队列联合使用。

我们来总结一下:

引用类型被垃圾回收时间用途生存时间对应包装类
强引用从来不会对象的一般状态JVM停止运行时终止正常类
软引用当内存不足时对象缓存内存不足时终止SoftReference
弱引用正常垃圾回收时对象缓存垃圾回收后终止WeakReference
虚引用正常垃圾回收时跟踪对象的垃圾回收垃圾回收后终止PhantomReference

一个软(弱)引用的例子

下面我们来使用软引用读取一张图片,读出图片的宽和高,代码如下:

package 软引用;  
  
import java.lang.ref.Reference;  
  
/** 
 * 封装引用对象的基类 
 * @author CodeingSnail 
 * 
 * @param <T> 
 */  
public abstract class ReferenceObject<T>{  
      
    public Reference<T> ref;  
      
    protected abstract T getInstance();  
    protected abstract Reference<T> getReference(T t);  
      
    /* 
     * 获取引用对象及弱引用 
     */  
    private T getRefrenceAndInstance(){  
        T t = getInstance();  
        getReference(t);  
        return t;  
    }  
      
    /** 
     * 获取被引用的对象 
     * @return 
     */  
    public T get(){  
        if(ref == null){  
            return getRefrenceAndInstance();  
        }  
        T t = ref.get();  
        if(t == null){  
            return getRefrenceAndInstance();  
        }  
        return t;  
    }  
      
    /** 
     * 清空对象的引用,回收对象 
     */  
    public void recycle() {  
        if(ref != null){  
            ref.clear();  
            ref = null;  
        }  
    }  
}  
import java.lang.ref.Reference;  
import java.lang.ref.SoftReference;  
  
import javax.swing.ImageIcon;  
  
  
/** 
 * 引用图片资源的类 
 * @author Administrator 
 * 
 */  
public class ReferenceBitmap extends ReferenceObject<ImageIcon>{  
    String url;  
    public ReferenceBitmap(String url) {  
        this.url = url;  
    }  
  
    @Override  
    protected Reference<ImageIcon> getReference(ImageIcon imageIcon) {  
        return new SoftReference<ImageIcon>(imageIcon);  
    }  
  
    @Override  
    protected ImageIcon getInstance() {  
        return new ImageIcon(url);  
    }  
      
}  
package 软引用;  
  
public class Client {  
      
    private ReferenceBitmap referenceBitmap;  
      
    public static void main(String[] args) {  
        Client client = new Client();  
        client.referenceBitmap = new ReferenceBitmap("E:\\test.png");  
        System.out.println("图片的高度是:" + client.referenceBitmap.get().getIconHeight());  
        System.out.println("图片的宽度是:" + client.referenceBitmap.get().getIconWidth());  
    }  
}  

在上面例子中我们先定义了一个引用的抽象类,并且给子类提供两个回调方法用来创建图片对象和所需要的软引用(也可以是弱引用),在get()方法中获取对象的实例。其实上面的例子是一个通用的方法,我们可以在getReference中定义我们需要的引用类型。假如我们现在已经很明确,我们要的就是软引用,可以将代码简化如下:

public class Client {  
    SoftReference<ImageIcon> softRefrence;  
    private ReferenceBitmap referenceBitmap;  
      
    public static void main(String[] args) {  
        Client client = new Client();  
        client.referenceBitmap = new ReferenceBitmap("E:\\test.png");  
        System.out.println("图片的高度是:" + client.referenceBitmap.get().getIconHeight());  
        System.out.println("图片的宽度是:" + client.referenceBitmap.get().getIconWidth());  
          
        //简化方法  
        client.softRefrence =   
                new SoftReference<ImageIcon>(new ImageIcon("E:\\test.png"));  
        System.out.println("图片的高度是:" + client.softRefrence.get().getIconHeight());  
        System.out.println("图片的宽度是:" + client.softRefrence.get().getIconWidth());  
    }  
}  

如何配合引用队列使用

我们下面来分析一下java.util包下的WeakHashMap类,打开JDK后会发现对这个类有一个很长的描述,我们来一起看一下大概意思。

Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use. More precisely, the presence of a mapping for a given key will not prevent the key from being discarded by the garbage collector, that is, made finalizable, finalized, and then reclaimed. When a key has been discarded its entry is effectively removed from the map, so this class behaves somewhat differently from other Map implementations.

这句话的大概意思是WeakHashMap的哈希表是基于Map接口的,其中key中保存的是value的一个弱引用。当系统回收了该key所对应的实际对象之后,WeakHashMap会自动删除该key对应的key-value对。

/** 
 * The entries in this hash table extend WeakReference, using its main ref 
 * field as the key. 
 */  
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {  
    V value;  
    final int hash;  
    Entry<K,V> next;  
  
    /** 
     * Creates new entry. 
     */  
    Entry(Object key, V value,  
          ReferenceQueue<Object> queue,  
          int hash, Entry<K,V> next) {  
        super(key, queue);  
        this.value = value;  
        this.hash  = hash;  
        this.next  = next;  
    }  
  
    @SuppressWarnings("unchecked")  
    public K getKey() {  
        return (K) WeakHashMap.unmaskNull(get());  
    }  
  
    public V getValue() {  
        return value;  
    }  
  
    public V setValue(V newValue) {  
        V oldValue = value;  
        value = newValue;  
        return oldValue;  
    }  
  
    public boolean equals(Object o) {  
        if (!(o instanceof Map.Entry))  
            return false;  
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;  
        K k1 = getKey();  
        Object k2 = e.getKey();  
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {  
            V v1 = getValue();  
            Object v2 = e.getValue();  
            if (v1 == v2 || (v1 != null && v1.equals(v2)))  
                return true;  
        }  
        return false;  
    }  
  
    public int hashCode() {  
        K k = getKey();  
        V v = getValue();  
        return Objects.hashCode(k) ^ Objects.hashCode(v);  
    }  
  
    public String toString() {  
        return getKey() + "=" + getValue();  
    }  
}  

WeakHashMap.Entry继承自WeakReference,在构造方法中可以看到直接将key交给WeakReference并通过ReferenceQueue关联。

/** 
 * 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;  
            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;  
            }  
        }  
    }  
}  

WeakHashMap中有一个私有的expungeStaleEntries方法,会在大部分共有方法中被调用,这个方法会将ReferenceQueue中所有失效的引用从Map中移除。WeakHashMap不会自动释放失效的引用,仅当包含了expungeStaleEntries方法被调用的时候才会释放。下面一个小例子来看一下WeakHashMap的使用:

public static void main(String[] args) {  
    WeakHashMap<String, String> map = new WeakHashMap<String, String>();  
    map.put(new String("1"), "1");  
    map.put("2", "2");  
    String s = new String("3");  
    map.put(s, "3");  
    int i = 0;  
    while(map.size() > 0){  
        try {  
            Thread.sleep(500);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("Map Size: " + map.size());  
        System.out.println(map.get("1"));  
        System.out.println(map.get("2"));  
        System.out.println(map.get("3"));  
        if(i == 3) s = null;  
        System.gc();  
        i++;  
    }  
}  

输出结果:

Map Size: 3  
1  
2  
3  
Map Size: 2  
null  
2  
3  
Map Size: 2  
null  
2  
3  
Map Size: 2  
null  
2  
3  
Map Size: 1  
null  
2  
null  
Map Size: 1  
null  

上面的例子中,第一个key外部没有强引用,则只打印了一次就被回收器回收,第三个key有外部的强引用,当我们将外部引用去掉后也被垃圾回收器回收,第二个key是被字符串常量池所引用,所以一直存在。