关于强引用、软引用、弱引用、幻象引用的区别,在不少公司的面试题中常常出现,可能有些小伙伴以为这个知识点比较冷门,但其实你们在开发中常常用到,如new一个对象的时候就是强引用的应用。php
在java语言中,除了原始数据类型(boolean、byte、short、char、int、float、double、long)的变量,其余全部都是所谓的引用类型,指向各类不一样的对象。理解这些引用的区别,对于掌握java对象生命周期和JVM内部相关机制很是有帮助。也有助于更深入的理解底层对象生命周期、垃圾收集机制等,对设计可靠的缓存框架、诊断应用OOM等问题也大有裨益。html
这四种应用主要的区别体如今对象不一样的可达性状态和对垃圾收集的影响,他们之间的可达性状态能够参看下图:java
强引用就是咱们最多见的普通对象引用(如new 一个对象),只要还有强引用指向一个对象,就代表此对象还“活着”。在强引用面前,即便JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),让程序异常终止,也不会靠回收强引用对象来解决内存不足的问题。对于一个普通的对象,若是没有其余的引用关系,只要超过了引用的做用域或者显式地将相应(强)引用赋值为null,就意味着此对象能够被垃圾收集了。但要注意的是,并非赋值为null后就立马被垃圾回收,具体的回收时机仍是要看垃圾收集策略的。面试
如Object obj = new Object();缓存
软引用相对强引用要弱化一些,可让对象豁免一些垃圾收集。当内存空间足够的时候,垃圾回收器不会回收它。只有当JVM认定内存空间不足时才会去回收软引用指向的对象。JVM会确保在抛出OOM前清理软引用指向的对象,并且JVM是很聪明的,会尽量优先回收长时间闲置不用的软引用指向的对象,对那些刚构建的或刚使用过的软引用指向的对象尽量的保留。基于软引用的这些特性,软引用能够用来实现不少内存敏感点的缓存场景,即若是内存还有空闲,能够暂时缓存一些业务场景所需的数据,当内存不足时就能够清理掉,等后面再须要时,能够从新获取并再次缓存。这样就确保在使用缓存提高性能的同时,不会致使耗尽内存。app
软引用一般能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,java虚拟机就会把这个软引用加入到与之关联的引用队列中。框架
Object obj = new Object(); SoftReference<Object> sf = new SoftReference<Object>(obj); obj = null; //有时候会返回null sf.get();
经过上面的代码能够看出sf是对obj的一个软引用,当sf对象尚未被销毁前,sf.get()能够获取到这个对象,若是已被销毁,则返回null。性能
正确使用软引用的示例代码以下:this
SoftReference<List<Foo>> ref = new SoftReference<List<Foo>>(new LinkedList<Foo>()); // somewhere else in your code, you create a Foo that you want to add to the list List<Foo> list = ref.get(); if (list != null) { list.add(foo); } else { // list is gone; do whatever is appropriate }
在使用软引用的时候必须检查引用是否为null。由于垃圾收集器可能在任意时刻回收软引用,若是不作是否null的判断,可能会出现NullPointerException的异常。线程
总的来讲,软引用是用来描述一些还有用但并不是必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常以前,将会把这些对象列进回收范围之中进行第二次回收。若是此次回收尚未足够的内存,才会抛出内存溢出异常。
弱引用指向的对象是一种十分临近finalize状态的状况,当弱引用被清除的时候,就符合finalize的条件了。弱引用与软引用最大的区别就是弱引用比软引用的生命周期更短暂。垃圾回收器会扫描它所管辖的内存区域的过程当中,只要发现弱引用的对象,无论内存空间是否有空闲,都会马上回收它。如同前面我说过的,具体的回收时机仍是要看垃圾回收策略的,所以那些弱引用的对象并非说只要达到弱引用状态就会立马被回收。
基于弱引用的这些特性,弱引用一样能够应用在不少须要缓存的场景。
Object obj = new Object(); WeakReference<Object> wf = new WeakReference<Object>(obj); obj = null; //有时候会返回null wf.get(); //返回是否被垃圾回收器标记为即将回收的垃圾 wf.isEnQueued();
幻象引用,也有被说成是虚引用或幽灵引用。幻象引用并不会决定对象的生命周期。即若是一个对象仅持有虚引用,就至关于没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。不能经过它访问对象,幻象引用仅仅是提供了一种确保对象被finalize之后,作某些事情的机制(如作所谓的Post-Mortem清理机制),也有人利用幻象引用监控对象的建立和销毁。
Object obj = new Object(); PhantomReference<Object> pf = new PhantomReference<Object>(obj); obj=null; //永远返回null pf.get(); //返回是否从内存中已经删除 pf.isEnQueued();
幻象引用的get方法永远返回null,主要用于检查对象是否已经从内存中删除。
经过上面对四种引用类型的分析,你可能发现有些对象即便不可达,但也并不是是“非死不可”的,这个时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:若是对象在进行可达性分析后发现没有与GC Roots相链接的引用链,那它将会被第一次标记而且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种状况都视为“没有必要执行”。
若是这个对象被断定为有必要执行finalize()方法,那么这个对象将会放置在一个叫作F-Queue的队列之中,并在稍后被一个由虚拟机自动创建的、低优先级的Finalizer线程去执行它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样作的缘由是,若是一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的状况),将极可能会致使F-Queue队列中其余对象永久处于等待,甚至致使整个内存回收系统奔溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,若是对象要在finalize()中成功拯救本身——只要从新与引用链上的任何一个对象创建关联便可。譬如把本身(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;若是对象这时候尚未逃脱,那基本上它就真的被回收了。
任何一个对象的finalize()方法都只会被系统自动调用一次,若是对象面临下一次回收,它的finalize()方法不会被再次执行。
对象的可达性是JVM垃圾收集器决定如何处理对象的一个重要考虑指标。
全部引用类型都是抽象类java.lang.ref.Reference的子类,子类里提供了get()方法。经过上面的分析中能够得知,除了幻象引用(由于get永远返回null),若是对象尚未被销毁,均可以经过get方法获取原有对象。其实有个很是关键的注意点,利用软引用和弱引用,咱们能够将访问到的对象,从新指向强引用,也就是人为的改变了对象的可达性状态。因此对于软引用、弱引用之类,垃圾收集器可能会存在二次确认的问题,以确保处于弱引用状态的对象没有改变为强引用。
可是有个问题,若是咱们错误的保持了强引用(好比,赋值给了static变量),那么对象可能就没有机会变回相似弱引用的可达性状态了,就会产生内存泄露。因此,检查弱引用指向对象是否被垃圾收集,也是诊断是否有特定内存泄露的一个思路,咱们的框架使用到弱引用又怀疑有内存泄露,就能够从这个角度检查。
对于软引用、弱引用、幻象引用能够配合引用队列(ReferenceQueue)来使用,特别是幻象引用,get方法只返回null,若是再不指定引用队列,基本就没有任何意义了。
上面分析了四种引用类型的使用,熟悉这几种应用类型对深刻理解JVM也大有裨益。
热门阅读:
【JVM从小白学成大佬】1.开篇
【JVM从小白学成大佬】2.Java虚拟机运行时数据区
参考:
《深刻理解Java虚拟机》