基于软引用设计的缓存案例
简介
JVM中的不同引用类型
Java虚拟机(JVM)中的引用分为四种类型:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。这里主要介绍强引用和软引用:
强引用(Strong Reference)
强引用是最常见的引用类型,通常使用new关键字创建对象时,就会生成一个硬引用。
只要强引用存在,垃圾收集器(GC)就不会回收这个对象,即使内存不足。
强引用是导致内存泄漏的主要原因之一,因为它们会阻止对象被回收。
软引用(Soft Reference)
软引用(Soft Reference)是Java中一种特殊的引用类型,它允许垃圾收集器(Garbage Collector, GC) 在内存不足时回收这些对象。
软引用通常用于实现内存敏感的缓存,如图片缓存等,当系统内存不足时,可以自动释放这些缓存对象,以避免内存溢出。
在java中,通常使用 new SoftReference<>(Object referent) 构造函数来创建软引用,referent是你想要软引用的对象
// 创建一个被软引用的对象
Object myObject = new Object();
// 创建一个软引用,指向上面创建的对象
SoftReference<Object> softReference = new SoftReference<>(myObject);
ReferenceQueue与软引用的回收
ReferenceQueue 是 Java 中的一个类,属于 java.lang.ref 包。它是一个队列,用于接收垃圾收集器回收对象的引用。当一个对象被垃圾收集器标记为即将回收时,如果存在指向该对象的引用(软引用、弱引用或虚引用),这个引用就会被加入到与之关联的 ReferenceQueue 中。
ReferenceQueue的机制为回收不彻底问题提供了一种解决方案,它允许程序员获取到已被回收的软引用类对象,并利用其进一步地实现深度回收或其他业务逻辑,提高了代码的灵活性。
案例介绍
本案例基于软引用及ReferenceQueue 技术,实现对Student类对象的缓存功能。
下面介绍缓存器类StudentCache中的关键方法:
cacheStudent(Student s) 方法:
- 功能:此方法用于将一个学生对象 s 缓存到一个集合中,使用软引用来引用学生对象。
- 步骤:
- 首先调用 cleanCache() 方法,清除已经被垃圾回收的软引用。
- 创建一个新的 StudentRef 对象,该对象包装了传入的学生对象 s 和引用队列 q。
- 将 StudentRef 对象通过学生的 ID 作为键,存储到 refs 哈希表中。
- 打印当前缓存中存储的学生数量。
public void cacheStudent(Student s){
cleanCache(); //清除垃圾引用
StudentRef ref = new StudentRef(s, q);
refs.put(s.getId(), ref);
System.out.println(refs.size());
}
getStudent(Long id) 方法:
- 功能:此方法用于从缓存中获取一个学生对象。
- 步骤:
- 检查 refs 映射中是否包含传入的 ID id。
- 如果包含,从映射中获取对应的 StudentRef 对象,并调用其 get() 方法尝试获取学生对象。
- 如果不包含,返回空。
- 返回获取到的学生对象。
public Student getStudent(Long id){
Student s = null;
// 检查缓存中是否有学生的软引用?
if(refs.containsKey(id)){
StudentRef ref = refs.get(id);
s = ref.get();
}
return s;
}
cleanCache() 方法:
- 功能:此方法用于清除已经被垃圾回收的软引用对象。
- 步骤:
- 使用一个循环,不断地从引用队列 q 中 poll() 取出软引用对象 StudentRef。
- 检查取出的软引用是否为 null,如果不为 null,则表示有软引用对象已经被垃圾回收。
- 从 refs 映射中移除被回收的软引用对象,使用 ref._key 作为键来定位并删除映射中的条目。
// 清除软引用的Student对象已经被回收的StudentRef对象
private void cleanCache(){
StudentRef ref = null;
while((ref = (StudentRef) q.poll()) != null){
refs.remove(ref._key);
}
}
操作说明
配置 JVM 命令,设置最大堆内存为10MB,以便于观察垃圾回收效果。
运行结果
随着对象的不断创建,始终并没有报Heap buffer overflow异常,且哈希表的 size 在一定区间内循环往复,证明垃圾回收器自动且彻底地完成了垃圾回收任务,避免了内存泄漏问题。
Full Code
Student.java
package org.rhythm.pojo.entity;
public class Student {
Long id;
String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Student() {
}
}
StudentCache.java
package org.rhythm.pojo.cache;
import org.rhythm.pojo.entity.Student;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
public class StudentCache {
// 继承SoftReference,使得每一个实例都具有可识别的标识
private class StudentRef extends SoftReference<Student>{
private Long _key = null;
public StudentRef(Student s, ReferenceQueue<? super Student> q) {
super(s, q);
_key = s.getId();
}
}
private static StudentCache instance = new StudentCache(); //单例模式实例对象
private Map<Long, StudentRef> refs; //用于Cache内容的储存
private ReferenceQueue<Student> q; //垃圾回收队列
// 构建缓存器实例
private StudentCache(){
refs = new HashMap<>();
q = new ReferenceQueue<>();
}
public static StudentCache getInstance() { return instance; }
public void cacheStudent(Student s){
cleanCache(); //清除垃圾引用
StudentRef ref = new StudentRef(s, q);
refs.put(s.getId(), ref);
System.out.println(refs.size());
}
public Student getStudent(Long id){
Student s = null;
// 检查缓存中是否有学生的软引用?
if(refs.containsKey(id)){
StudentRef ref = refs.get(id);
s = ref.get();
}
return s;
}
// 清除软引用的Student对象已经被回收的StudentRef对象
private void cleanCache(){
StudentRef ref = null;
while((ref = (StudentRef) q.poll()) != null){
refs.remove(ref._key);
}
}
}
CacheTest.java
package org.rhythm.pojo;
import org.rhythm.pojo.cache.StudentCache;
import org.rhythm.pojo.entity.Student;
public class CacheTest {
public static void main(String[] args) {
for (Long i = 0L; ; i++) {
StudentCache.getInstance().cacheStudent(new Student(i, String.valueOf(i)));
}
}
}