java中的强,软,弱,虚引用(及利用软引用实现高速缓存)

在java中引用的类型一共有四种,分别是:强引用,软引用,弱引用和虚引用。
那么他们各自的定义是什么呢?
1.强引用(StrongReference)
强引用是使用最普通的应用。如果一个对象具有强引用,那么gc绝不会回收它。当内存空间不足,java虚拟机宁愿抛出OOM(OutOfMemory),使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
2.软引用(SoftReference):
如果一个对象只具有软引用,则内存空间足够,gc就不会回收它。如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可以用来实现内存敏感的高速缓存。(下有例子)。
软引用常用的用法:软引用可以和一个应用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,java虚拟机就会把这个软引用加入到与之关联的引用队列中。
3.弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在gc线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否充足,都会回收它的内存。不过,由于gc是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,java虚拟机就会把这个弱引用加入到与之关联的引用队列中。这点跟软引用的常用用法没有什么不同。
4.虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

ReferenceQueue queue = new ReferenceQueue();
    PhantomReference pr = new PhantomReference(new Object(),queue);

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
使用软引用来实现高速缓存
需要使用软引用的应用场景
我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息。作为一个用户,我们完全有可能需要回头去查看几分钟甚至几秒钟前查看过的雇员档案信息(同样,我们在浏览WEB页面的时候也经常会使用“后退”按钮)。这时我们通常会有两种程序实现方式:一种是把过去查看过的雇员信息保存在内存中,每一个存储了雇员档案信息的Java对象的生命周期贯穿整个应用程序始终;另一种是当用户开始查看其他雇员的档案信息的时候,把存储了当前所查看的雇员档案信息的Java对象结束引用,使得垃圾收集线程可以回收其所占用的内存空间,当用户再次需要浏览该雇员的档案信息的时候,重新构建该雇员的信息。很显然,第一种实现方法将造成大量的内存浪费,而第二种实现的缺陷在于即使垃圾收集线程还没有进行垃圾收集,包含雇员档案信息的对象仍然完好地保存在内存中,应用程序也要重新构建一个对象。我们知道,访问磁盘文件、访问网络资源、查询数据库等操作都是影响应用程序执行性能的重要因素,如果能重新获取那些尚未被回收的Java对象的引用,必将减少不必要的访问,大大提高程序的运行速度。

我们通过一个雇员信息查询系统的小例子来说明如何构建一种高速缓存器来避免重复构建同一个对象带来的性能损失。我们将一个雇员的档案信息定义为一个Employee类:

package com.mydoctest;

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class Employee {
    private String id;// 雇员的标识号码
    private String name;// 雇员姓名
    private String department;// 该雇员所在部门
    private String Phone;// 该雇员联系电话
    private int salary;// 该雇员薪资
    private String origin;// 该雇员信息的来源

    // 构造方法
    public Employee(String id) {
        this.id = id;
        getDataFromlnfoCenter();
    }

    // 到数据库中取得雇员信息
    private void getDataFromlnfoCenter() {
        //通过sleep来模拟从数据库或者文件中查信息的耗时操作
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            System.out.println("sleep error");
            e.printStackTrace();
        }
    }
    public String getID(){
        return id;
    }

}

这个Employee类的构造方法中我们可以预见,如果每次需要查询一个雇员的信息。哪怕是几秒钟之前查询过的,都要重新构建一个实例,这是需要消耗很多时间的。下面是一个对Employee对象进行缓存的缓存器的定义:

package com.mydoctest;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.Hashtable;

public class EmployeeCache{
    static private EmployeeCache cache;//一个cache实例
    private Hashtable<String,EmployeeRef> employeeRefs;//用于cache内容的存储
    private ReferenceQueue<Employee> q;//垃圾reference的队列

    //继承softreference,使得每一个实例都具有可识别的标识
    //并且该标识与其在hashmap内的key相同

    private class EmployeeRef extends SoftReference<Employee>{
        private String _key = "";

        public EmployeeRef(Employee em, ReferenceQueue<? super Employee> q) {
            super(em, q);
            _key=em.getID();
        }
    }


    //构建一个缓存器实例
    private EmployeeCache(){
        employeeRefs = new Hashtable<String,EmployeeRef>();
        q = new ReferenceQueue<Employee>();
    }

    //取得缓存器实例
    public static EmployeeCache getInstance(){
        if(cache == null){
            cache = new EmployeeCache();
        }
        return cache;
    }

    //以软引用的方式对一个employee对象的实例进行引用并保存该引用
    private void cacheEmployee(Employee em){
        cleanCache();//清除垃圾引用
        EmployeeRef ref = new EmployeeRef(em,q);
        employeeRefs.put(em.getID(),ref);
    }

    //依据所指定的ID号,重新获取相应的employee对象的实例
    public Employee getEmployee(String ID){
        Employee em = null;
        //缓存中是否有该employee实例的软引用,如果有,从软引用中取得
        if(employeeRefs.containsKey(ID)){
            EmployeeRef ref = employeeRefs.get(ID);
            em = ref.get();
        }

        //如果没有软引用,或者从软引用中取得的实例是null
        //重新构建一个实例,并保存对这个新建实例的软引用
        if(em==null){
            em=new Employee(ID);
            System.out.println("retrieve from employeeinfocenter.id"+ID);
            this.cacheEmployee(em);
        }
        return em;
    }


    //清除那些所软引用的employee对象已经被回收的employeeRef对象
    private void cleanCache() {
        EmployeeRef ref = null;
        while((ref= (EmployeeRef) q.poll())!=null){
            employeeRefs.remove(ref._key);
        }
    }

    //清除cache内的全部内容
    public void clearCache(){
        cleanCache();
        employeeRefs.clear();
        System.gc();
        System.runFinalization();
    }
}




我的测试代码为:

package test3;

public class Main {

    public static void main(String[] args) {
        EmployeeCache cache = EmployeeCache.getInstance();
        System.out.println("start");
        System.out.println("k1 start:"+System.currentTimeMillis());
        System.out.println(cache.getEmployee("1").getID());
        System.out.println("k1 end:"+System.currentTimeMillis());
        System.out.println("k2 start:"+System.currentTimeMillis());
        System.out.println(cache.getEmployee("2").getID());
        System.out.println("k2 end:"+System.currentTimeMillis());
        System.out.println("k3 start:"+System.currentTimeMillis());
        System.out.println(cache.getEmployee("1").getID());
        System.out.println("k3 end:"+System.currentTimeMillis());
        System.out.println("k4 start:"+System.currentTimeMillis());
        System.out.println(cache.getEmployee("2").getID());
        System.out.println("k4 end:"+System.currentTimeMillis());
        cache.clearCache();
        System.out.println("after clear");
        System.out.println("k5 start:"+System.currentTimeMillis());
        System.out.println(cache.getEmployee("1").getID());
        System.out.println("k5 end:"+System.currentTimeMillis());
        System.out.println("k6 start:"+System.currentTimeMillis());
        System.out.println(cache.getEmployee("2").getID());
        System.out.println("k6 end:"+System.currentTimeMillis());
    }

}

下面是运行结果:

start
k1 start:1453194918981
retrieve from employeeinfocenter.id1
1
k1 end:1453194921982
k2 start:1453194921982
retrieve from employeeinfocenter.id2
2
k2 end:1453194924982
k3 start:1453194924982
1
k3 end:1453194924982
k4 start:1453194924982
2
k4 end:1453194924983
after clear
k5 start:1453194925010
retrieve from employeeinfocenter.id1
1
k5 end:1453194928010
k6 start:1453194928010
retrieve from employeeinfocenter.id2
2
k6 end:1453194931010

证明确实能够缓存,并且节省了时间。
另外补充相关的对象可及性的知识。
对象可及性的判断
在很多时候,一个对象并不是从根集直接引用的,而是一个对象被其他对象引用,甚至被几个对象所引用,从而构成一个以根集为顶的树形结构。如图:
这里写图片描述

在这个树形的引用链中,箭头的方向代表了引用的方向,所指向的对象是被引用对象。由图可以看出,从根集到一个对象可以由很多条路径。比如到达对象5的路径就有①-⑤,③-⑦两条路径。由此带来了一个问题,那就是某个对象的可及性如何判断:
◆单条引用路径可及性判断:在这条路径中,最弱的一个引用决定对象的可及性。
◆多条引用路径可及性判断:几条路径中,最强的一条的引用决定对象的可及性。
比如,我们假设图2中引用①和③为强引用,⑤为软引用,⑦为弱引用,对于对象5按照这两个判断原则,路径①-⑤取最弱的引用⑤,因此该路径对对象5的引用为软引用。同样,③-⑦为弱引用。在这两条路径之间取最强的引用,于是对象5是一个软可及对象。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
JAVA引用类型主要包括引用引用引用引用。 1. 引用(Strong Reference):最常见的引用类型。只要某个对象引用与之关联,垃圾回收器就不会回收该对象。例如:Object obj = new Object(),这时obj是一个引用,指向了一个对象。 2. 引用(Soft Reference):对于只有引用关联的对象,在内存不足时,垃圾回收器可能会回收这些对象引用可以通过 SoftReference 类来创建。例如:SoftReference<Object> softRef = new SoftReference<>(obj)。 3. 引用(Weak Reference):比引用一些,只有引用关联的对象在垃圾回收时,不论内存是否充足,都会被回收。引用可以通过 WeakReference 类来创建。例如:WeakReference<Object> weakRef = new WeakReference<>(obj)。 4. 引用(Phantom Reference):最的一种引用关系。引用对象无法通过引用直接访问,也无法获取对象的任何属性或方法。主要作用是在对象被垃圾回收时,收到一个系统通知。引用可以通过 PhantomReference 类来创建。例如:PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue)。 这些引用类型在内存管理起到不同的作用。引用是最常用的,其他引用类型通常用于辅助内存管理,例如在缓存对象池等场景使用引用引用,或者在处理直接内存时使用引用
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值