强引用、软引用、弱引用、虚引用的概念、区别、应用

一、四大引用级别的概念

  • 强引用:就是正常的引用,类似于下面:
    • Object object = new Object();,object就是一个强引用,gc是不会清理一个强引用引用的对象的,即使面临内存溢出的情况。
  • 软引用:SoftReference,GC会在内存不足的时候清理引用的对象:
    • SoftReference reference = new SoftReference(object); object = null;
  • 弱引用:GC线程会直接清理弱引用对象,不管内存是否够用:
    • WeakReference reference = new WeakReference(object); object = null;
  • 虚引用:和弱引用一样,会直接被GC清理,而且通过虚引用的get方法不会得到对象的引用,形同虚设,这里弱引用是可以的:
    • PhantomReference refernce = new PhantomReference(object); object = null;

二、四大引用级别之间的区别

强引用和软引用

  • 这个比较简单,软引用只有在内存不足的时候才会被清理,而强引用什么时候都不会被清理(程序正常运行的情况下),即使是内存不足,利用这一个特性,可以做一些缓存的工作,下面的应用会讲到。

软引用和弱引用

  • 弱引用不会影响GC的清理,也就是说当GC检测到一个对象存在弱引用也会直接标记为可清理对象,而软引用只有在内存告罄的时候才会被清理

弱引用和虚引用

  • 说这个之前要说一下ReferenceQueue的概念,ReferenceQueue是一个队列,初始化Reference的时候可以作为构造函数的参数传进去,这样在该Reference的referent域(Reference用来保存引用对象的属性)指向的引用对象发生了可达性的变化时会将该Reference加入关联的队列中,这个具体的变化根据Reference的不同而不同。官方APi对ReferenceQueue的介绍:
    • Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.
    • 简单翻译就是:当reference object所引用的对象发生了适当的可达性变化时,GC向该队列追加该引用对象(reference object)。
  • 弱引用和虚引用的区别就在于被加入队列的条件不同,这里主要侧重于考虑对象所属的类重写了finalize方法,将对象的状态归纳为三种:finalizable, finalized、reclaimed,分别代表:未执行finalize函数、已经执行finalize函数,已经回收。如果没有重写finalize函数的话下面再考虑。
  • 虚引用必须和一个ReferenceQueue联合使用,当GC准备回收一个对象的时候,如果发现该对象还有一个虚引用,就会将这个虚引用加入到与之关联的队列.
  • 弱引用:当GC第一次试图回收该引用指向的对象时会执行该对象的finalize方法,然后将该引用加入队列中,但是该引用指向的对象是可以在finlize函数中“复活”的,所以即使通过Reference的get方法得到的是null,而且reference被加入到了ReferenceQueue,这个对象仍然是存活的,这种现象是有点违背对象正常生命周期的,下面以代码示例:
package com;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class Main {
    static Weak weak;
    static class Weak {
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize() is called");
            weak = this;//复活对象
            System.out.println("after finalize ,object is alive again");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        weak = new Weak();
        ReferenceQueue queue = new ReferenceQueue<>();
        WeakReference weakReference = new WeakReference(weak, queue);
//        PhantomReference weakReference = new PhantomReference<>(weak,queue);
        if (weakReference.get() == null) {
            System.out.println("before gc : reference is not available");
        } else {
            System.out.println("before gc : reference is available");
        }
        weak = null;
        System.gc();//执行GC线程
        Thread.sleep(3000);
        if (weakReference.get() == null) {
            System.out.println("after gc : reference is not available");
        } else {
            System.out.println("after gc : reference is available");
        }
        if (queue.poll() == null) {
            System.out.println("after gc : reference is not in queue");
        } else {
            System.out.println("after gc : reference is in queue");
        }
        weak=null;
        System.gc();//再次执行GC
        Thread.sleep(3000);
        if (queue.poll() == null) {
            System.out.println("gc agaain : reference is not in queue");
        } else {
            System.out.println("gc agaain : reference is in queue");
        }
  }    
}
  • output:
    before gc : reference is available
    finalize() is called
    after finalize ,object is alive again
    after gc : reference is not available
    after gc : reference is in queue
    gc agaain : reference is not in queue
  • 可以看到,对象在复活之后,reference不可用,而且reference已经被加入到队列中。
  • 虚引用:虚引用只有在对象处于reclaimed状态时才会将相关reference加入到队列,可以根据这个特性检测对象准确的生命周期,比如可以知道对象精确的销毁时间。
  • 和上面相同的代码,输出如下:
    before gc : reference is not available
    finalize() is called
    after finalize ,object is alive again
    after gc : reference is not available
    after gc : reference is not in queue
    gc again : reference is in queue
  • 我们可以根据输出看到明显的区别:
    • 弱引用可以得到对象的引用,而虚引用不可以
    • 弱引用在第一次被GC清理的时候会调用finalize方法,然后将reference加入到队列,即使这时候的对象是存货状态,而虚引用在第二次GC执行,对象处于reclaimed状态时才会将reference加入到队列。
  • 顺便提一下软引用,在这个方面,软引用和弱引用是一样的,也就是说即使加入到了队列中,也不能确定对象是否销毁。

三、四大引用的应用

软引用

  • 缓存敏感数据
    • 适用情景:一个web应用,查询联系人数据,如果在浏览的过程中点击了返回上一条查询结果,正常处理就是再重新查询一次,然而这样是很浪费时间的,所以如果能将上一次的查询结果缓存下来就会极大的提升性能,所以可以将查询结果保存为软引用,这样在内存充足的情况下GC都不会释放掉这个引用指向的对象,下次需要结果的时候可以先判断一下这个对象是否被释放,如果没有就可以直接利用不用再次查询。
    • 当软引用所指向的对象被回收的时候,通过get方法返回的是null,如果在构造软引用的时候传入了一个ReferenceQueue,就是将这个reference加入到队列,然后我们可以清理这些无用的reference。实际上我们最好这样做,否则会造成大量无用reference导致的内存泄漏。
  • 下面举一个缓存的例子:
package com;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
public class Main {
}
class People{
    String id;
    String name;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
class PeopleCache {
    private static PeopleCache singlonCache;
    private HashMap refChache;//用来存储缓存的数据
    private ReferenceQueue queue;
    //自定义引用类,增加属性_key,方便在缓存数据中查找
    static class PeoReference extends SoftReference {
        private String _key;
        public PeoReference(People referent, ReferenceQueue q) {
            super(referent, q);
            _key = referent.getId();
        }
    }
    private PeopleCache(){
        this.queue=new ReferenceQueue();
        this.refChache=new HashMap<>();
    }
    public static PeopleCache getInstance(){
        if(singlonCache==null){
            singlonCache=new PeopleCache();
        }
        return singlonCache;
    }
    public void cachePeople(People people){
        cleanCache();//清除已经标记为垃圾的引用
        PeoReference reference = new PeoReference(people, queue);
        refChache.put(people.getId(), reference);//将对象的软引用保存到缓存中
    }
    public void cleanCache(){
        PeoReference reference = null;
        while ((reference = (PeoReference)queue.poll())!=null){
            refChache.remove(reference._key);
        }
    }
    public People getCachedPeople(String key){
        People people = null;
        if (refChache.containsKey(key)){
            people= (People) refChache.get(key).get();
            System.out.println("get object from cache");
        }else{
            people = new People();
            System.out.println("get object from database or web server");
        }
        return people;
    }
}
  • 当需要查询数据的时候先获取一个PeopleCache实例,然后使用getCachedPeople方法试图去获取指定ID的数据,如果缓存中有的话就从缓存中获取,没有的话就正常查询获取,并且将查询结果缓存到HashMap。

弱引用

  • WeakHashMap
    • 具体使用和HashMap一样,但是它的键存放的是对象的弱引用,如果该弱引用指向的对象被垃圾回收了,WeakHashMap就会删除对应的数据(键值对)。
    • 用来缓存那些非必需存在的数据。

虚引用

你可能感兴趣的:(强引用、软引用、弱引用、虚引用的概念、区别、应用)