基于软引用设计的缓存案例

简介

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,以便于观察垃圾回收效果。

image

image

运行结果

随着对象的不断创建,始终并没有报Heap buffer overflow异常,且哈希表的 size 在一定区间内循环往复,证明垃圾回收器自动且彻底地完成了垃圾回收任务,避免了内存泄漏问题。

image

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)));
        }
    }
}
posted @ 2024-08-24 21:12  AnUpdatingHam  阅读(3)  评论(0编辑  收藏  举报