在java
语言中,除了原始数据类型(boolean
、byte
、short
、char
、int
、float
、double
、long
)的变量,其他所有都是所谓的引用类型,指向各种不同的对象。在 《深入了解JVM
虚拟机中》说,java对象的引用类型有四种: 强引用,软引用,弱引用,虚引用。本文就详细的解释下这四种引用类型。
强引用
如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存不足, JVM
就会抛出OOM
. 如果想让这类对象被回收,只需要将其置为null
或者 超出对象的生命周期范围,就可以被回收。具体什么时间回收取决于GC算法。
这一特性在 java.util.ArrayList
的 clear()
方法中有引用:
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
我们都知道,java.util.ArrayList
中有一个私有变量 elementData
数据,在调用clear
方法清空数据时,clear
方法是将 elementData
的每个元素置为null
. 不同于 elementData=null
, 强引用仍然存在. 避免在后续调用add()
等方法条件元素的时候,进行重新的内存分配. 将每个elementData
元素置为null
可以及时释放内存。
软引用
如果一个对象支持软引用,内存空间足够,垃圾回收器就不会回收它,当内存空间不够用的时候,就会回收这些对象。软引用用SoftReference来实现。
private static void test1() {
Object o = new Object();
// 软引用
SoftReference softRefer = new SoftReference<>(new Object());
// 查看下内存地址
System.out.println("softRefer=" + softRefer.get());
try {
byte[] bytes = new byte[6 * 1024 * 1024];
} catch (Throwable e) {
System.out.println("===============>error:" + e.getMessage());
} finally {
// 再次考察内存地址
System.out.println("softRefer.get()=" + softRefer.get());
// 查看强引用对象
System.out.println("o:" + o);
}
}
例子:Mybatis
中的本地缓存SoftCache
就是使用softReference
模式,本地内存不足时会回收cache
中的对象。
软引用技术使得Java
系统可以更好地管理内存,保持系统稳定,防止内存泄漏,避免系统崩溃,因此在处理一些内存占用大而且生命周期长使用不频繁的对象可以使用该技术。
弱引用
对于生存周期相对比较长而且重新创建的时候开销少的对象,弱引用也比较有用,和软引用对象不同的是,垃圾回收器如果碰到了WeakReference
对象,不管内存是否充足,都会释放WeakReference
对象的内存,但是垃圾回收器需要运行很多次才能够找到WeakReference
对象。
比如 ThreadLocal
这个类的 Entry
实体,就是一个 弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal
中,获取到线程私有对象是通过线程持有的一个threadLocalMap
,然后传入ThreadLocal
当做key
获取到对象的,这时候就有个问题,如果你在使用完ThreadLocal
之后,将其置为null
,这时候这个对象并不能被回收,因为他还有 ThreadLocalMap->entry->key
的引用,直到该线程被销毁,但是这个线程很可能会被放到线程池中不会被销毁,这就产生了内存泄露,jdk
是通过弱引用来解决的这个问题的,entry
中对key
的引用是弱引用,当你取消了ThreadLocal
的强引用之后,他就只剩下一个弱引用了,所以也会被回收。
弱引用对象在使用的时候,可以配合ReferenceQueue
类使用,如果弱引用被回收,JVM就会把这个弱引用加入到相关的引用队列中去。如果我们通过ReferenceQueue
类来构造一个弱引用,当弱引用的对象已经被回收的时候,系统将自动使用对象引用队列来代替对象引用,而且我们可以通过ReferenceQueue
类的运行来决定是否真正要从垃圾回收器里面将该死引用(Dead Reference
)清除。
虚引用
虚引用要通过java.lang.ref.PhantomReference
类来实现,虚引用不会决定对象的生命周期,如果一个对象只有虚引用,就相当于没有引用,在任何时候都可能会被垃圾回收器回收。它不能单独使用也不能访问对象,虚引用必须和引用队列联合使用。
虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供一种确保对象被finalize
以后,做某些事情的机制。
我们来看一个例子:
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
System.out.println(reference.get());
输出结果是:
null
可以看出: 无法通过虚引用来获取对一个对象的真实引用。这也是虚引用的重要的特性。
创建虚引用对象,我们除了把包裹的对象传了进去,还传了一个ReferenceQueue
,从名字就可以看出它是一个队列。虚引用的特点之二就是虚引用必须与ReferenceQueue
一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue
中。
在NIO
中,使用虚引用来管理堆外内存。




