前言: 本文首先介绍强引用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构造方法传入了ReferenceQueue,则在回收该对象之后,相应的SoftReference实例会被add进referenceQueue: Student stu = new Student(); 通过从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缓存信息。 2. 查询缓存时,若hashMap中没有相应studentNum的Student对象缓存,那么就加载student信息并新构建Student对象通过SoftReference引用存入hashMap。 3. StudentCache对象通过静态内部类的方式构造单例进行管理,保证线程安全。 4. 当StudentCache缓存需要清除时,调用destroy方法,清除hashMap中对student对象引用的SoftReferences。
//数据源,代表数据库 为了方便演示缓存效果,我们将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实例将被释放。 来源:http://www./content-1-111501.html |
|