java中SoftReference与WeakReference应用于高速缓存示例

前言:

本文首先介绍强引用StrongReference、软引用SoftReference、弱引用WeakReference与虚引用PhantomReference之间的区别与联系;

并通过一个高速缓存的构建方案,来了解SoftReference的应用场景。

本文参考书籍Thinking in Java以及多篇博文。


一、Reference分类

Reference即对象的引用,根据引用的不同类型,对JVM的垃圾回收有不同的影响。

1. 强引用StrongReference

通常构建对象的引用都是强引用,例如

Student stu = new Student();

stu就是对这个新实例化的Student对象的强引用。

当对象根节点可及(reachable),且存在强引用(栈 或者 静态存储区)指向该对象时,GC无法回收该对象内存,直至内存不足发生了OOM(out of memory Error):

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

当该对象未被引用了,才会被GC回收。

2. 软引用SoftReference

软引用通过SoftReference实例构建对其他对象的引用。不同于强引用,当JVM内存不足即将发生OOM时,在GC过程若对象根节点可及、不存在强引用指向该对象、且存在软引用指向该对象,则该对象会被GC回收:

Student stu = new Student();SoftReference<Student> softRef = new SoftReference<Student>(stu);stu = null;/*此时若发生GC,Student对象只有一个软引用softRef指向它,若内存此时即将OOM,则该Student实例将被回收*/

若SoftReference构造方法传入了ReferenceQueue,则在回收该对象之后,相应的SoftReference实例会被add进referenceQueue:

Student stu = new Student();ReferenceQueue<Student> studentReferenceQue = new ReferenceQueue<Student>();SoftReference<Student> softRef = new SoftReference<Student>(stu, studentReferenceQue ); stu = null; /*在内存不足GC,该Student实例被回收时,SoftReference实例softRef将被add进referenceQueue*///SoftReference<Student> softRefFromQueue = (SoftReference<Student>)studentReferenceQue.poll(); 

通过从referenceQueue中poll出Reference对象,即可知softReference所引用的Student对象已经被回收了。

3. 弱引用WeakReference

弱引用级别比软引用更低。当对象根节点可及、无强引用和软引用、有弱引用指向对象时,若发生GC,该对象将直接被回收:

Student stu = new Student();
ReferenceQueue<Student> studentReferenceQue = new ReferenceQueue<Student>();
WeakReference<Student> softRef = new WeakReference<Student>(stu, studentReferenceQue );
stu = null;
/*此时发生GC*/
System.gc();
/*则Student实例将被直接回收,且WeakReference实例将被加入studentReferenceQue中*/

/*通过从studentReferenceQue中poll出Reference对象,即可知Student实例已经被回收*/
//Reference<Student> studentRef = (Reference<Student>)studentReferenceQue.poll();

4. 虚引用PhantomReference

虚引用对对象的声明周期不产生任何影响,对JVM无任何内存回收的暗示。

其使用主要用于跟踪对象的回收情况。

二、Reference应用:高速缓存构建

当某一类数据数量巨大,存于数据库或者文件中,运行内存不足以承受加载全部数据的开销时,缓存是一个比较好的方案。

根据程序的局部性原理,某一时刻使用的数据,在短时间内被使用的概率比较大。因此我们可以在使用某条数据时将其从数据库/硬盘上加载进内存。

但是随着程序运行时间变久,缓存也越来越多,将会对内存消耗影响不断增大,因此也需要构建机制将老的缓存数据清除,减小缓存对进程内存占用的影响。

通过软引用SoftReference构建缓存是个比较好的方案,正常使用时,数据被加载进内存并由SoftReference引用;当内存不足时,GC会将SoftReference引用的对象回收,从而达到保护内存的目的。

下面是一个高速缓存的案例:

首先是缓存对象数据结构Student:

class Student {
    /*Fields*/
    private String studentNumber;
    private String name;
    private int age;

    public String getStudentNumber() {
        return studentNumber;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Student(String studentNumber, String name, int age) {
        this.studentNumber = studentNumber;
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "studentNumber=‘" + studentNumber + ‘\‘‘ +
                ", name=‘" + name + ‘\‘‘ +
                ", age=" + age +
                ‘}‘;
    }
}

下面是缓存类:

public class StudentCache {

    /*Constructor*/
    public StudentCache() {
        studentCacheHashMap = new HashMap<String, StudentReference>();
        studentReferenceQueue = new ReferenceQueue<Student>();
    }

    /* for student cache*/
    private HashMap<String, StudentReference> studentCacheHashMap;
    /* for GC trace */
    private ReferenceQueue<Student> studentReferenceQueue;

    /*Singleton*/
    public static StudentCache getInstance()
    {
        return InnerClassStudentCache._INSTANCE;
    }

    private static class InnerClassStudentCache
    {
        public static final StudentCache _INSTANCE = new StudentCache();
    }

    /*Cache Interface*/
    public Student getCachedStudent(String studentNumber)
    {
        cleanGCedCache();
        //不存在该Student缓存
        if(!studentCacheHashMap.containsKey(studentNumber))
        {
            //构造Student实例
            /*从数据库中读取该student信息,然后构造Student。此处为了方便,使用测试类StudentDataSource作为辅助*/
            Student stu = StudentDataSource.getStudent(studentNumber);
            if(null == stu)
                return null;
            String studentNum = stu.getStudentNumber();
            String name = stu.getName();
            int age = stu.getAge();
            Student student = new Student(studentNumber, name, age);

            //通过Reference加入缓存
            StudentReference studentReference = new StudentReference(student, studentReferenceQueue);
            studentCacheHashMap.put(studentNum, studentReference);
        }
        //从缓存中获取StudentReference,并获取Student强引用作为返回值
        return studentCacheHashMap.get(studentNumber).get();
    }

    /* clean cached students which is GCed from hashMap */

    private static class StudentReference extends SoftReference<Student>
    {
        public StudentReference(Student referent, ReferenceQueue<? super Student> q) {
            super(referent, q);
            this.studentId = referent.getStudentNumber();
        }

        /* 在GC回收Student之后,此Reference对象被放入ReferenceQueue,加标识以识别是哪个student对象被回收 */
        public final String studentId;
    }

    private void cleanGCedCache()
    {
        StudentReference studentReference = null;
        while ((studentReference = (StudentReference)studentReferenceQueue.poll()) != null)
        {
            //将已回收的Student对象从cache中移除
            studentCacheHashMap.remove(studentReference.studentId);
            System.out.println("student " + studentReference.studentId + " has been GCed, and found in referenceQueue.");
        }
    }

    public void destroy()
    {
        // 清除Cache
        cleanGCedCache();
        studentCacheHashMap.clear();
        System.gc();
        System.runFinalization();
    }
}

缓存类说明:

1. HashMap<String, StudentReference> studentCacheHashMap 用来保存Student缓存信息。
  既然是缓存,肯定要给每个数据一个标识,这里的key选择Student.studentNum;
  value是SoftReference对象,之所以构建SoftReference<Student>的派生类添加字段studentNum作为域的StudentReference作为value,是因为当发生GC且student被清掉时,
  我们需要判断出是哪个student实例被回收了,从而进一步从hashMap中清除该student实例的其他缓存信息。
  该SoftReference对象引用了真正的Student对象,除了该软引用之外,没有其他引用指向Student对象,从而可以在内存不足OOM前回收这些student对象,释放出内存供使用。

2. 查询缓存时,若hashMap中没有相应studentNum的Student对象缓存,那么就加载student信息并新构建Student对象通过SoftReference引用存入hashMap。
  若hashMap中已存在该student信息,那么证明缓存已经存在,直接通过SoftReference获取Student的强引用作为返回值。

3. StudentCache对象通过静态内部类的方式构造单例进行管理,保证线程安全。

4. 当StudentCache缓存需要清除时,调用destroy方法,清除hashMap中对student对象引用的SoftReferences。

测试类:
//数据源,代表数据库class StudentDataSource
{
    static HashMap<String, Student> students;
    static
    {
        students = new HashMap<String, Student>();
        students.put("SX1504001", new Student("SX1504001", "ZhangSan", 25));
        students.put("SX1504002", new Student("SX1504002", "LiSi", 25));
        students.put("SX1504003", new Student("SX1504003", "WangWu", 25));
        students.put("SX1504004", new Student("SX1504004", "LiuLiu", 25));
        students.put("SX1504005", new Student("SX1504005", "AAAAAA", 25));
        students.put("SX1504006", new Student("SX1504006", "BBBBBB", 25));
        students.put("SX1504007", new Student("SX1504007", "CCCCCC", 25));
        students.put("SX1504008", new Student("SX1504008", "DDDDDD", 25));
        students.put("SX1504009", new Student("SX1504009", "EEEEEE", 25));
        students.put("SX1504010", new Student("SX1504010", "FFFFFF", 25));
        //.....
    }

    static Student getStudent(String studentNum)
    {
        return students.get(studentNum);
    }
}

为了方便演示缓存效果,我们将StudentReference的基类SoftReference暂时改为WeakReference,从而达到每次GC都直接将其回收的效果,方便观察。
并构建以下测试用例:

class Tester
{
    public static void main(String[] args) {
        //通过cache访问student
        AccessOneStudentFromCache("SX1504001");

        //假设JVM某刻自动GC了
        System.gc();
        sleep();

        //再次通过cache访问student
        AccessOneStudentFromCache("SX1504002");
    }

    static void AccessOneStudentFromCache(String studentNum)
    {
        StudentCache studentCache = StudentCache.getInstance();
        System.out.println(“Now access student:” + studentCache.getCachedStudent(studentNum));
    }

    static void sleep()
    {
        try{
           Thread.currentThread().sleep(10);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 

运行结果如下:

Now access student:Student{studentNumber=‘SX1504001‘, name=‘ZhangSan‘, age=25}
student SX1504001 has been GCed, and found in referenceQueue.
Now access student:Student{studentNumber=‘SX1504002‘, name=‘LiSi‘, age=25}

Process finished with exit code 0

可以看到,在GC之后,WeakReference指向的SX1504001 Student对象已经被回收了。

同理,在StudentReference的基类为SoftReference<Student>时,当OOM发生时,缓存中的所有student实例将被释放。

原文地址:https://www.cnblogs.com/xinxinBlog/p/10357890.html

时间: 2024-10-29 22:37:20

java中SoftReference与WeakReference应用于高速缓存示例的相关文章

Java中PhantomReference、WeakReference、SoftReference有什么区别?

Java中有多种引用类型,按照从强到弱的顺序分别如下: 强引用:就是最常见的引用,通过等号赋值就是强引用. 软引用:当内存不足时自动释放引用.一般应用于需要大量内存的缓存程序中. 弱引用:不阻止垃圾回收,当强引用或者软引用都消失时,即使存在弱引用内存也会被释放. 幽灵引用:只引用一个对象的"灵魂",幽灵引用是不能直接访问的,所以get方法永远返回null.这种引用能阻止JVM释放对象的内存,但是被引用的对象可以finalize.这东西有什么用呢?按照文档中的说法就是用于调整对象之间fi

java 中Int和Integer区别以及相关示例

Java是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入不是对象的基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer, 从JDK 1.5(5.0)开始引入了自动装箱/拆箱机制,使得二者可以相互转换. Java 为每个原始类型提供了包装类型: 原始类型: boolean, char,        byte,short,int,       long,float,d

java中的List详解以及代码示例

一:概念List是Java集合Collection中的一个接口,一般用ArrayList类和LinkedList类去实现这个接口.Collection集合还有其他接口:Map,Set(在我的另一篇博客)二:LIST的使用List的常用方法 boolean add(E e) //尾插 e void add(int index, E element) //将 e 插入到 index 位置 boolean addAll(Collection<? extends E> c) //尾插 c 中的元素 E

Java中RSA非对称密钥加解密使用示例

一.简介: RSA加密算法是最常用的非对称加密算法,CFCA在证书服务中离不了它.RSA是第一个比较完善的公开密钥算法,它既能用于加密,也能用于数字签名.这个算法经受住了多年深入的密码分析,虽然密码分析者既不能证明也不能否定RSA的安全性,但这恰恰说明该算法有一定的可信性,目前它已经成为最流行的公开密钥算法. 二.RSA的公钥.私钥的组成,以及加密.解密的公式可见于下表 三.使用方式: ①  假设A.B机器进行通信,已A机器为主: ②  A首先需要用自己的私钥为发送请求数据签名,并将公钥一同发送

Java中的七种排序方式代码示例

package baseJava; /** * @title SortMethods.java * @author DonsenChen * @Date 2018年5月2日 上午10:16:03 * @Description */ public class SortMethods { public static void main(String[] args) { int[] arr = { 3, 7, 9, 1, 4, 8, 2, 6, 5 }; binarySort(arr); bubble

Java中使用ServerSocket和Socket的多线程示例

开始...1.服务器端: public class ServerSocketApp { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务已启动"); while (true) { Socket socket = serverSocket.accept(); System.out.println(

Java中的数组数据结构需要了解的要点

. 首先,数组是Java中的对象.它们不是像int.short或long这样的基本类,也不是具有很多方法的全功能对象,但由于它们是对象,所以它们隐式地扩展了Object,这就是为什么可以使用数组引用(例如toString())调用java.lang.object的任何方法. Java中数组的另一个重要之处是,一旦创建,就不能更改数组的大小.好奇的开发人员可能会问,我们如何在Java中拥有像ArrayList这样的动态集合,可以在饱和的时候调整自身大小?好吧,扩容不像你想得那样,可以简单地增加一个

Java中引用类 strong reference .SoftReference 、 WeakReference 和 PhantomReference的区别

当在 Java 2 平台中首次引入 java.lang.ref 包,其中包含 SoftReference . WeakReference 和 PhantomReference 三个引用类,引用类的主要功能就是能够引用仍可以被垃圾收集器回收的对象.在引入引用类之前,我们只能使用强引用(strong reference).举例来说,下面一行代码显示的就是强引用 obj : Object obj = new Object(); obj 这个引用将引用堆中存储的一个对象.只要 obj 引用还存在,垃圾收

java中虚引用PhantomReference与弱引用WeakReference(软引用SoftReference)的差别

之前的这篇博客介绍了java中4种引用的差别和使用场景,在最后的总结中提到: "软引用和弱引用差别不大,JVM都是先把SoftReference和WeakReference中的referent字段值设置成null,之后加入到引用队列:而虚引用则不同,如果某个堆中的对象,只有虚引用,那么JVM会将PhantomReference加入到引用队列中,JVM不会自动将referent字段值设置成null".这段总结写的比较仓促,也没有给出实际的例子加以佐证.本文主要是重申下这几种引用的差别,并