之前在研究MVP的时候接触过弱引用,用来避免由于Activity被回收导致的内存泄露,但是当时只是浅尝辄止,没有系统的去接触引用这一块,没想到最近在看ThreadLocal的时候又看到关于引用的东西了,于是干脆就把这一块的东西都整理一下。
每种语言都有自己的数据处理方式,比如在c或c++中我们经常使用指针来间接的操纵对象,而在Java中,操纵的标识符实际上是指向一个对象的“句柄”。我们可以将这想象成用遥控器(句柄)去操纵电视机(对象),只要握住这个遥控板,就相当于掌握了与电视机连接的通道。而如果需要“换频道”或者“关小声音”,我们实际操纵的是遥控板(句柄),再由遥控板(句柄)自己操纵电视机(对象)。比如:
String controller = "Controller";
其中controller就是句柄,我们可以通过它来操纵它所指向的字符串对象。
而引用实际上代表着句柄和对象之间的关系,这种关系会影响到垃圾回收器对这个对象的回收时机。
在很多时候,我们对于一个对象的坚挺程度的要求是不一样的,这通常表现为我们希望的垃圾回收器对它的回收时机的不同。对于一些比较重要的对象,我们希望垃圾回收器永远不去回收它,即使此时内存空间已经不足了,因为一旦它被回收,将导致严重的后果。而对于一些不那么重要的对象,比如在做图片缓存的时候生成的大量图片的缓存对象,我们希望垃圾回收器只在内存不足的情况下去对它进行回收以提升用户体验。
而我们知道,在Java中垃圾回收器的运行是JVM操作的,但是我们仍然可以在一定程度上与垃圾回收器进行交互,其目的在于更好的帮助垃圾回收器管理好应用的内存。这种交互方式就是使用JDK 1.2引入的java.lang.ref包,为对象指定不同的引用类型。
Java中实际上有四种强度不同的引用,从强到弱它们分别是,强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference)。
强引用其实就是我们平时写的最多新建对象的那种用法,你也许根本就没有意识到这个就是传说中的强引用:
Lypeer lypeer = new Lypeer();
上面就创建了一个 Lypeer 对象,并且在这个对象和 lypeer(句柄)之间建立了强引用的关系。
强引用是所有的引用里面最强的一种,这意味着只要这个引用还存在,那么垃圾回收器就绝对不会回收这个对象。即使内存已经不足,JVM宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
软引用 (Soft Reference) 在强度上弱于强引用,通过类SoftReference来表示:
SoftReference weakLypeer = new SoftReference(new Lypeer());
通过这种方式我们就已经在 weakLypeer(句柄) 和一个新建的 Lypeer 对象之间建立了软引用。值得注意的是,在新建 SoftReference 对象的时候,可以是像上面一样传一个完全新建的对象进去,也可以是传一个句柄进去,但如果是传句柄进去的话那个对象就不仅仅是只有软引用与之连接了,而垃圾回收器回收它的时候是看它最强的那个引用来决定回收时机的。
软引用的作用是告诉垃圾回收器,我新建的这个对象其实不那么重要(至少没有强引用简历的对象重要)。当JVM中的内存不足的时候,垃圾回收器可以释放这些只被软引用所指向的对象。如果全部释放完这些对象之后,内存还不足,才会抛出OutOfMemory错误。因此,软引用非常适合于创建缓存。当系统内存不足的时候,缓存中的内容是可以被释放的。比如考虑一个图像编辑器的程序,该程序会把图像文件的全部内容都读取到内存中,以方便进行处理。而用户也可以同时打开多个文件。当同时打开的文件过多的时候,就可能造成内存不足。如果使用软引用来指向图像文件内容的话,垃圾回收器就可以在必要的时候回收掉这些内存。
如果是让强引用来做这件事想想会有什么后果?强引用会强制图片数据留在内存,这时候就需要我们自己来决定什么时候图片数据不被需要并且手动的使这个强引用失效,从而可以让垃圾回收器进行回收。因此我们其实被强制着做了垃圾回收器该做的事,并且人为的决定应该清理到哪个对象。这样的话我们和c/c++程序员还有什么分别(感觉会被撕)?
弱引用简单来说就是将对象留在内存的能力不是那么强的引用。使用WeakReference,垃圾回收器会帮你来决定引用的对象何时回收并且将对象从内存移除。创建弱引用如下:
WeakReference weakLypeer = new WeakReference(new Lypeer());
建立弱引用之后,通过 weakLypeer.get() 方法就可以得到 Lypeer 对象。但是由于弱引用并不能阻挡垃圾回收器对对象进行回收,如果此对象只有弱引用的话,你有可能会发现使用 get() 方法时突然返回 null 。
弱引用的作用在于解决强引用所带来的对象之间在存活时间上的耦合关系。弱引用最常见的用处是在集合类中,尤其在哈希表中。哈希表的接口允许使用任何Java对象作为键来使用。当一个键值对被放入到哈希表中之后,哈希表对象本身就有了对这些键和值对象的引用。如果这种引用是强引用的话,那么只要哈希表对象本身还存活,其中所包含的键和值对象是不会被回收的。如果某个存活时间很长的哈希表中包含的键值对很多,最终就有可能消耗掉JVM中全部的内存。
“虚引用”顾名思义,就是形同虚设,与其他几种引用不同的是,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue) 联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中,所以通过检查该队列里面的内容就知道一个对象是不是已经准备要被回收了。
虚引用及其队列的使用情况并不多见,主要用来实现比较精细的内存使用控制,这对于移动设备来说是很有意义的。
在有些情况下,程序会需要在一个对象的可达到性发生变化的时候得到通知。比如某个对象的强引用已经不存在了,只剩下软引用或是弱引用,但是还需要对引用本身做一些的处理。典型的情景是在哈希表中。引用对象是作为 WeakHashMap 中的键对象的,当其引用的实际对象被垃圾回收之后,就需要把该键值对从哈希表中删除。有了引用队列,就可以方便的获取到这些弱引用对象,将它们从表中删除。在软引用和弱引用对象被添加到队列之前,其对实际对象的引用会被自动清空。通过引用队列的 poll/remove 方法就可以分别以非阻塞和阻塞的方式获取队列中的引用对象。
由于引用这一块和 JVM 的关系比较密切,而博主本身对这一块的东西其实没什么深入的研究,所以这篇博文里讲的东西可能略显粗浅,如果哪里有什么不正确的地方还望不吝赐教,拜谢。