前言
从Jdk1.2开始,在java.lang.ref包下就提供了三个类:SoftReference(软引用),PhantomReference(虚引用)和WeakReference(弱引用),它们分别代表了系统对对象的中的三种引用方式:软引用,虚引用以及弱引用。因此java语言对对象的引用有如下四种。可能有些工作经验比较丰富的java程序员都不太明白这几种引用的区别,仅仅只是知道而已。但是知道弱引用和软引用的概念以及如何使用它们是两回事,引用类在垃圾回收工作上有着重要的作用。对于经验不够丰富的小白以及那些和我经验差不多的程序员来说,他们在第一次听说这些引用类的时候,我想它们应该和我是一样的感觉:wtf?这是个什么东西?我怎么学了java那么久都没听说过?
再说说我为什么会看这个的?我司的产品其实前身是一个测试,我真是想破了头,我都想不出来为什么公司会让一个测试做产品。好了,言归正传,以下是我和我司的产品对话:
产品: 小李啊,我们这个app需要加一个读取银行卡的功能,你抽空做一下。具体要求是进入这个界面的时候,用户就可以开始读卡了,读卡的方法XX已经给你封装好了,你直接调用就可以了。
我: 好的,没问题。大概半个小时做完。内心窃喜,握草,这简直是太简单了,还好我之前做过循环轮播条的功能,还记得当初的思路,就是用Handler发送延时消息就可以了,这个功能嘛,照葫芦画瓢,直接在读卡失败的回调接口中发消息,继续调用读卡方法就行了,一直读, 直到读卡成功后跳出循环。哎呀,我简直是天才。bingo!搞定!
半个小时候,我司产品皱着眉头走过来。。
产品: 哎,小李,你做的这个读卡功能是不是有问题啊,最典型的一个问题就是,没加这个app之前,app本身还运行挺流畅,怎么加了这个读卡的功能,app没点进去读卡页面一次,app就感觉慢一点,你赶紧看看这个是什么原因。
我: 这怎么可能?我写的代码肯定是没问题的(真是赤裸的打脸)。
半个小时后
产品: 怎么样,小李,问题找到了吗?
我: 问题找到了,我马上解决。
究其原因就是因为设计是一进来读卡页面就调用读卡方法读卡,如果读卡失败了,那么就会一直利用Handler发消息,而按下返回键的时候,由于Handler还持有读卡页面(Activity)的引用,导致读卡页面内存无法回收而导致的内存泄漏(OOM)问题。于是各种上google查解决办法,其中有一种解决办法就是利用引用去解决。而我对这方面又不是太了解,所以就花了点时间来了解一下,总结一下。至于Handler导致的内存泄漏的原因和解决办法,我将在另外一篇博客中详细讲解。
引用的应用场景
- 我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型。这正是Java中弱引用和软引用的主要区别。
- 如果一个对象只有弱引用指向它,垃圾回收器会立即回收该对象,这是一种急切回收方式。
- 相对的,如果有软引用指向这些对象,则只有在JVM需要内存时才回收这些对象。
- 弱引用和软引用的特殊行为使得它们在某些情况下非常有用。
- 例如:软引用可以很好的用来实现缓存,当JVM需要内存时,垃圾回收器就会回收这些只有被软引用指向的对象。
- 而弱引用非常适合存储元数据,例如:存储ClassLoader引用。如果没有类被加载,那么也没有指向ClassLoader的引用。一旦上一次的强引用被去除,只有弱引用的ClassLoader就会被回收。
强引用(StrongReference)
- 强引用是我们在编程过程中使用的最简单的引用,如代码String s=”abc”中变量s就是字符串对象”abc”的一个强引用。
- 任何被强引用指向的对象都不能被垃圾回收器回收,这些对象都是在程序中需要的。
- StringBuffer stringBuffer = new StringBuffer(); 此时的object也是一个强引用。我们在开发过程中使用的最多的就是强引用。这个就不做过多的说明了。
弱引用(WeakReference)
- 弱引用通过WeakReference类实现,弱引用和软引用很像但是弱引用的级别更低。
- 对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存(立即回收的方式)。当然并不是说当一个对象只有弱引用时,它就会立即被回收—-正如那些使用引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收。
String str = new String("helloworld"); WeakReference weakReference = new WeakReference(str); str = null; 全部代码如下: public class Main { public static void main(String[] args) { String str = new String("Struts2权威指南"); //创建一个弱引用,让这个弱引用引用到“Struts2权威指南”字符串 WeakReference weakReference = new WeakReference(str); //切断str引用和“Struts2权威指南”字符串之间的引用 str = null; //取出弱引用所引用的对象 System.out.println(weakReference.get()); //强制进行垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用所引用的对象 System.out.println(weakReference.get()); } } 执行的结果为:“Struts2权威指南”和null
- 由下面的代码我们可以看到,程序首先创建了一个“Struts2权威指南”对象,并且让str引用变量引用它。 WeakReference weakReference = new WeakReference(str); 时,系统首先创建了一个弱引用,和str指向同一个对应,当程序执行str = null;的时候,程序剪断了str和“Struts2权威指南”之间的联系,此时“Struts2权威指南”这时只有一个弱引用weakReference指向它,这个时候我们的程序依然可以通过这个弱引用来访问该字符串常量,程序的第一个System.out.println(weakReference.get())依然是可以输出“Struts2权威指南”的,接下来程序强制垃圾回收,如果系统垃圾回收器启动,那么将只有弱引用所引用的对象会被清除掉,当程序执行到第二个System.out.println(weakReference.get())的时候,通常输出的就只是null值了。
- 总结: 当我们将str赋值为空,该对象就可以被垃圾回收器回收。因为该对象此时不再含有其他强引用,即使指向该对象的弱引用WeakReference也没有办法阻止垃圾回收器对该对象的回收。相反的,如果该对象还有软引用,str不会被立即回收,除非JVM需要内存。
软引用(SoftReference)
软引用需要通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统将会回收它。软引用通常用于对内存敏感的程序中。
public class Main {
public static void main(String[] args) {
String str = new String("Struts2权威指南");
//创建一个软引用,让这个弱引用引用到“Struts权威指南”字符串
SoftReference softReference = new SoftReference(str);
//切断str引用和“Struts2权威指南”字符串之间的引用
str = null;
//取出软引用所引用的对象
System.out.println(softReference.get());
//强制进行垃圾回收
System.gc();
System.runFinalization();
//再次取出软引用所引用的对象
System.out.println(softReference.get());
}
}
输出结果是:“Struts2 权威指南”和“Struts2权威指南”
- 总结:软引用的代码执行过程和弱引用大致相同。根据打印的内容就说明了当JVM内存充足的时候,JVM是不会考虑去回收软引用指向的内存空间的。我们可以注意到,在上面的代码中,有一行代码是为str = null,虽然这行代码是不能阻止垃圾回收器回收对象,但是可以延迟回收,这点和弱引用是不相同的。这也是软引用适合做缓存而弱引用适合存储元数据的根本原因所在。
- 一个使用弱引用的典型例子是WeakHashMap,它是除HashMap和TreeMap之外,Map接口的另一种实现。WeakHashMap有一个特点:map中的键值(keys)都被封装成弱引用,也就是说一旦强引用被删除,WeakHashMap内部的弱引用就无法阻止该对象被垃圾回收器回收。
虚引用 (PhantomReference)
- 虚引用通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大的影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时。那它和没有引用的效果大致相同。虚引用主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,虚引用必须和引用队列(ReferenceQueue)联合使用。
public class Main2 { public static void main(String[] args) { //创建一个字符串引用 String str = new String("Struts2权威指南"); //创建一个引用队列 ReferenceQueue referenceQueue = new ReferenceQueue(); //创建一个虚引用 让次虚引用引用到“Struts2权威指南”字符串 PhantomReference phantomReference = new PhantomReference(str, referenceQueue); //切断str和“Struts2权威指南”之间的引用关系 str = null; //取出虚引用所引用的对象,并不能通过虚引用访问被引用的对象, //所以此处输出的应该是null System.out.println(phantomReference.get()); //强制进行垃圾回收 System.gc(); System.runFinalization(); //取出引用队列最先进入队列中的引用于phantomReference进行比较 System.out.println(referenceQueue.poll() == phantomReference); } } 输出结果: null 和 true
- 因为系统无法通过虚引用来获取被引用的对象,所以在执行第一个System.out.println(phantomReference.get())的时候,打印出来的是空值(即使此时系统并未进行强制垃圾回收)。当程序强制垃圾回收后,只有虚引用引用的字符串对象将会被垃圾回收,当被引用的对象被回收后,对象的引用将被添加到关联的引用队列中去(也就时说,虚引用指向的字符串对象会被垃圾回收器回收,而自己本身将被添加到与之关联的引用队列中去),因此我们才在第二个System.out.println(referenceQueue.poll() == phantomReference)中看到输出的true
引用队列(ReferenceQueue)
- 引用队列由java.lang.ref.ReferenceQueue类来表示,它用于保存被回收后对象的引用。当把软引用、弱引用和引用队列联合使用的时候,系统在回收被引用的对象之后,将把被回收对象对应的应用添加到关联的引用队列中。与软引用和弱引用不同的是,虚引用在对象被释放之后,将把已经回收对象对应的虚引用添加它的关联引用队列中,这是得可以在对象被回收之前采取行动。
- 软引用和弱引用可以单独使用,但是虚引用不能单独使用,单独使用虚引用没有太大的意义。虚引用的主要作用就是跟踪对象被垃圾回收的状态,程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用,从而了解虚引用所引用对象是否即将被回收。
总结
- 在应用程序中,我们使用引用类可以边面在程序执行期间将对象留在内存中。如果我们一软引用,弱引用或虚引用的方式引用对象,这样垃圾收集器就能够随意的释放对象。如果希望尽可能的减少程序在其生命周期中所占的内存大小时,这些引用类就很有好处。
- 当然必须指出的一个问题: 要使用这些特殊的引用类,就不能保留对对象的强引用。如果保留了对对象的强引用,那么就会浪费这些类所提供的任何好处。
- 四种引用最主要的区别就是垃圾回收器回收的时机不同: 强引用: 我们经常使用的一种引用。基本上垃圾回收器不会主动的去回收。弱引用: 垃圾回收器会立刻回收弱引用。软引用: 在JVM没有出现内存不足的情况下,垃圾回收器不会去主动回收软引用。虚引用: 虚引用引用的字符串会被垃圾回收器回收, 自己本身会被添加到关联的引用队列中去。
- 以上是我自己查找资料对这方面的全部理解。如果有错误,还望各位指正。希望对不了解这块的小伙伴们有所帮助。
原文链接:https://blog.csdn.net/lqw_student/article/details/52947125