利用软引用和引用队列构建软引用缓存

来源:互联网 发布:python 日期加减 月份 编辑:程序博客网 时间:2024/06/13 11:48

在项目开发过程中,有些时候我们希望将数据存放到缓存中,这样可以快速进行读取。但是,当JVM中内存不够用时,我们又不希望缓存数据会占用到JVM的内存。本文利用软引用和引用队列的特性来构建软引用缓存以满足上述的应用场景。

1、软引用(SoftReference)和引用队列(ReferenceQueue)
软引用是java.lang.ref中提供的一个比较有意思的类。通过软引用占有的对象,JVM会将其标记为软可达。在JVM发生垃圾回收时,会对软可达的对象区别对待。只要当JVM即将发生内存溢出时,才会将软可达的对象回收。软引用可以配合引用队列进行使用,当软引用占有的对象被回收后,JVM会将该软引用对象放入引用队列中。
软引用的使用方式如下:

    // 直接创建一个软引用对象    SoftReference<T> ref = new SoftReference<T>(T t);    // 创建一个软引用对象,并配合ReferenceQueue使用    SoftReference<T> ref = new SoftReference<T>(T t, ReferenceQueue refQueue);    // 对于软引用占有的值,可通过get获取    // 如果值已经被JVM回收,则返回null    T t = ref.get();

2、构建软引用缓存
在有些时候,我们希望将数据存放到缓存中,这样可以快速进行读取。但是,当JVM中内存不够用时,我们又不希望缓存数据会占用到JVM的内存。面对这样的业务场景,通过构建软引用实现的缓存,可以达到以上的目的。

/** * 定义缓存规范 *  * @author Administrator * * @param <K> * @param <V> */public interface Cache<K, V> {    public V get(K key);    public boolean set(K key, V value);}
/** * 定义软引用缓存的基本实现 *  * @author Administrator * * @param <K> * @param <V> */public abstract class SoftRefCache<K, V> implements Cache<K, V> {    // 缓存,用软引用记录    private ConcurrentHashMap<K, ExtraInfoReference<V>> cache = new ConcurrentHashMap<K, ExtraInfoReference<V>>();    private ReferenceQueue<V> refQueue = new ReferenceQueue<V>();    public V get(K key) {        V value = null;        if (cache.containsKey(key)) {            ExtraInfoReference<V> refValue = cache.get(key);            value = refValue.get();        }        // 如果软引用被回收        if (value == null) {            // 清除软引用队列            clearRefQueue();            // 创建值并放入缓存            value = createValue(key);            ExtraInfoReference<V> refValue = new ExtraInfoReference<V>(key, value, refQueue);            cache.put(key, refValue);        }        return value;    }    /**     * 实现set方法     */    public boolean set(K key, V value) {        ExtraInfoReference<V> refValue = new ExtraInfoReference<V>(key, value, refQueue);        cache.put(key, refValue);        return true;    }    /**     * 定义创建值的方法     * @return     */    protected abstract V createValue(K key);    /**     * 从软引用队列中移除无效引用,     * 同时从cache中删除无效缓存     */    @SuppressWarnings("unchecked")    protected void clearRefQueue() {        ExtraInfoReference<V> refValue = null;        while((refValue = (ExtraInfoReference<V>) refQueue.poll()) != null) {            K key = (K) refValue.getExtraInfo();            cache.remove(key);        }    }}
/** * 继承自软引用: * 用来存储额外的信息 *  * @author Administrator * * @param <T> */public class ExtraInfoReference<T> extends SoftReference<T> {    private Object info;    public ExtraInfoReference(Object info, T t, ReferenceQueue<T> refQueue) {        super(t, refQueue);        this.info = info;    }    public Object getExtraInfo() {        return this.info;    }}

在以上代码中,定义了一个接口用来规范缓存的实现;同时定义一个软引用缓存类实现缓存接口。在软引用缓存类中,使用ConcurrentHashMap来存放缓存数据。其中值是自定义的软引用类ExtraInfoReference,可存储额外的信息。此处用来存放缓存中的key,可辅助clearRefQueue()方法进行无效缓存的回收。同时定义createValue(K key)方法需要自定义实现,用来创建缓存数据。

3、测试

public class TestSoftRefCache extends SoftRefCache<String, byte[]>{    /**     * 创建10M大小的缓存数据     */    @Override    public byte[] createValue(String key) {        byte[] bytes = new byte[1024*1024*10];  //10M        return bytes;    }}

TestSoftRefCache继承实现SoftRefCache,其中createValue(String key) 中用来创建10M大小的缓存。

public class SoftRefCacheTest {    public static void main(String[] args) {        // 创建软引用缓存        TestSoftRefCache cache = new TestSoftRefCache();        // for循环10次,相当于向内存中放入100M的数据        for (int i = 0; i < 10; i++) {            cache.get(String.valueOf(i));        }    }}

在测试软引用缓存时,我们循环10次,向缓存中放入100M的数据,同时设置JVM的内存参数-Xms100M, -Xmx100M。可以预计到,当循环到第10次时,由于JVM内存放不下最后一个缓存数据,将发生一次full gc,此时会回收存活时间比较长的缓存数据。

测试结果:
当程序运行到第10个循环时,进入clearRefQueue()方法的while循环体中。此时refQueue的长度为7,表示由7个软引用对象被回收掉。如图1所示。
这里写图片描述
图1 ReferenceQueue中存放对象无效SoftReference
当clearRefQueue()结束之后,缓存中还保留有2个缓存,key值为’8’和’9’的对象。如图2所示。
这里写图片描述
图2 缓存中剩余数据
同样,可以通过-XX:+PrintGCDetails打印出内存回收情况,如图3所示。
这里写图片描述
图3 内存回收情况
从内存回收中可以看到,每循环2次之后,再第三次向缓存中放入数据时,会发生一个young gc。但是堆内存并没有明显的下降,说明缓存中的数据并没有发生回收。当循环到第9次时,发生一次full gc,此时堆内存由82403k下降到473k,大概是80M左右的数据,说明前面8份缓存都被回收了。最后在程序退出时,打印出内存使用情况,发现在年轻代中尚存有20M左右的数据,即full gc之后存入的2份缓存数据。
至此,我们便利用软引用和引用队列构建出软引用缓存。

总结
本文利用软引用及引用队列构建出软引用缓存。在项目开发过程中,有些时候我们希望将数据存放到缓存中,这样可以快速进行读取。但是,当JVM中内存不够用时,我们又不希望缓存数据会占用到JVM的内存。软引用缓存主要适用于以上的业务场景,比如存放类中属性和方法之间的关系等。但是需要注意的时,由于软引用缓存的存在,有可能导致full gc的快速到来。
在Java中,还提供了弱引用、虚引用。弱引用占有的对象比软引用拥有更短的生命周期,在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;对于虚引用占有的对象,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收,虚引用可用来跟踪对象被垃圾回收的活动。
对于许多希望有临时缓存,但又内存敏感的应用,可类似地创建虚引用缓存,以达到尽快回收缓存的目的。

0 0
原创粉丝点击