首先还是从问题入手:这个四个引用有什么区别,具体使用场景是什么?
在Java中,除了基本数据类型的变量(int ,double等),其他都是引用类型,指向不同的对象(类、接口或者数组等复杂的数据结构)。在这里拓展一下,浅拷贝和深拷贝的区别就是在clone对象时,是否clone引用类型的数据变量(记个小笔记)。
其实不同的引用类型的具体作用体现在JVM中,主要影响对象的可达性和GC回收。
强引用:
常见的普通引用,像Class c =new Class();c就是一个强引用指向Class对象。只要还有强引用指向对象,那就表明对象还活着,当JVM内存不足时,JVM宁愿OOM,使程序终止,也不会回收具有强引用的对象;相反对于一个普通对象,如果没有其他的引用指向该对象,只要超过了引用的作用域(比如作用域为方法级或者块级,程序运行完了方法后)或者显式地将强引用赋值为null,GC就会回收该对象。
软引用:
相对强引用弱化一些的引用,可以让对象避免一些垃圾回收。只有当JVM判定内存不足时,才会去回收软引用指向的对象:即JVM确保在发生OOM之前,会去清理软引用对象。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象
软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。(例如图片缓存)
弱引用:
弱引用并不能是对象避免垃圾收集,仅仅提供了一种访问对象的途径。这就可以用来构建一种没有特定约束的关系,如果试图获取的对象还在,就使用它,否则重新实例化。弱引用同样可以和一个引用队列联合使用。
弱引用同样是很多缓存实现的选择。
虚引用:
虚引用有的地方也翻译成幻象引用。不能通过它去访问对象,虚引用仅仅提供了一种确保对象被finalize后,做某些事情的机制。比如Post-Mortem 清理机制。当为对象设置虚引用时,该对象被GC回收时,会收到一个系统通知,从而来跟踪对象的GC回收情况。
延伸一波:
(1). 所有的引用类型都是继承自抽象类 java.lang.ref.Reference;看看里面的get方法
可以看到,如果对象没有被销毁,就可以通过get方法获取到原有对象当然虚引用除外(因为它的get返回null),所以我们可以将通过软引用和弱引用访问到的对象重新指向强引用,也就是认为的改变对象的可达性状态。因此对于软引用、弱引用之类,垃圾收集器可能会存在二次确认的问题,以保证处于弱引用状态的对象,没有改变为强引用。
(2). 引用队列(ReferenceQueue)
我们在创建各种引用并关联到响应对象时,可以选择是否需要关联引用队列,JVM 会在特定时机将引用 enqueue 到队列里,我们可以从队列里获取引用进行相关后续逻辑。尤其是幻象引用,get 方法只返回 null,如果再不指定引用队列,基本就没有意义了。
gc准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。利用引用队列,我们可以在对象处于相应状态时(对于幻象引用,就是前面说的被 finalize 了,处于幻象可达状态),执行后期处理逻辑。
(3). 拓展一种特殊情况:通常情况下,如果第一个对象没有指向强引用就符合GC回收标准,但是有时候对象本身并没有强引用,但是它的部分属性还在被使用。所以需要一个方法,在没有强引用情况下,通知JVM对象是在被使用的。在JAVA 9中可以按照下面代码来实现:
action()执行依赖对象的部分属性,假如我们直接
因为没有强引用指向Resource对象,所以JVM对它进行finalize是合法的,因此需要利用reachbilityFence方法保护该对象,声明该对象为强可达。
android中具体的应用:利用软、弱引用防止内存泄漏
常用的比如handler、AsyncTask等。
改进的handler写法:(利用静态内部类和弱引用防止内存泄漏)