Java中的引用
在JDK 1.2以前,Java中的引用的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用, 一个对象在这种定义下只有被引用或者没有被引用两种状态
但无法描述这样的对象:当内存空间还足够时,则能保留在内存之中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象
在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、 弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
强引用
在程序代码之中普遍存在,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象,当内存空间不足,JVM宁愿抛出OutOfMemoryError错误, 使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题,如:
Object strongReference = new Object();
如果强引用对象不使用时,需要弱化从而使GC能够回收,如:
strongReference = null;
显式地设置strongReference对象为null,或让其超出对象的生命周期范围,则GC认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于GC算法
在一个方法的内部有一个强引用,这个引用保存在Java栈中,而真正的引用内容(Object)保存在Java堆中。当这个方法运行完成后,就会退出方法栈, 则引用对象的引用数为0,这个对象会被回收。如:
public void test() {
Object strongReference = new Object();
// 省略其他操作
}
但是如果这个strongReference是全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。
以ArrayList为例:
在ArrayList类中定义了一个elementData数组,在调用clear方法清空数组时,每个数组元素被赋值为null。不同于ArrayList<Object> arr = null
,
强引用仍然存在,避免在后续调用add()等方法添加元素时进行内存的重新分配。
软引用
软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。 如果这次回收还没有足够的内存,才会抛出内存溢出异常。
只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<String>(str);
当GC准备回收一个对象时,如果发现它还仅有软引用(或弱引用,或虚引用)指向它,就会在回收该对象之前,把这个软引用(或弱引用,或虚引用)
加入到与之关联的引用队列(ReferenceQueue)中。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中
(调用ReferenceQueue的poll()
方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象)
就说明该引用对象所指向的对象被回收了
当内存不足时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收,垃圾收集线程会在虚拟机抛出OutOfMemoryError之前回收软引用对象, 而且虚拟机会尽可能优先回收长时间闲置不用的软引用对象。对那些刚构建的或刚使用过的较新的软对象会被虚拟机尽可能保留,这就是引入引用队列ReferenceQueue的原因。
应用场景:三级缓存、图片缓存框架中,“内存缓存”中的图片是以这种引用来保存、邮件报警
弱引用
弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。 当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象
如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;
回收时,JVM首先将软引用中的对象引用置为null,然后通知垃圾回收器进行回收:
str = null;
System.gc();
下面的代码会让一个弱引用再次变为一个强引用:
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
// 弱引用转强引用
String strongReference = weakReference.get();
和软引用一样,弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:
-
在静态内部类中,经常会使用虚引用。例如,一个类发送网络请求,承担callback的静态内部类,则常以虚引用的方式来保存外部类(宿主类)的引用, 当外部类需要被JVM回收时,不会因为网络请求没有及时回来,导致外部类不能被回收,引起内存泄漏。
-
ThreadLocal中的静态内部类 ThreadLocalMap Entry 存储数据用到了弱引用,解决内存泄漏的问题,
虚引用
虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知,与其他几种引用都不同,虚引用并不会决定对象的生命周期。 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 创建虚引用,要求必须与一个引用队列关联
PhantomReference pr = new PhantomReference(str, queue);
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收之前会收到一条系统通知。
Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用
当垃圾回收器回收时,某些对象会被回收,某些不会被回收。垃圾回收器会从根对象Object来标记存活的对象,然后将某些不可达的对象和一些引用的对象进行回收。