文章目录
- 1 强引用
- 2 软引用
- 3 弱引用
- 4 虚引用
- 总结
在 Java 日常开发中,我们每时每刻都在使用“引用”,准确的说是普遍只使用强引用,但 Java 不仅仅只有强引用。
从 JDK 1.2 开始,Java 就提供了四种强度不同的引用,即强引用、软引用、弱引用和虚引用。使用不同的引用,可以更灵活的操作内存,控制对象的生命周期。
1 强引用
强引用一般是指把一个对象赋给一个引用变量,是最普遍的引用。只要强引用存在,GC时就不会回收被引用的对象。如果内存空间不足,JVM 会抛出 OOM 异常。如果强引用对象不再使用了,需要弱化才能使其被GC回收。
public static void main(String[] args) throws InterruptedException {
Object a = new Object(); // 强引用
Object b = a; // 强引用
a = null; // 赋值为null,即弱化
System.gc();
Thread.sleep(1000);
System.out.println(a); // a为null
System.out.println(b); // java.lang.Object@66a3ffec,b强引用a原来指向的对象,不被GC回收
}
2 软引用
软引用是一种相对强引用弱化了的引用,通过 java.lang.ref.SoftReference 类来实现,用来描述一些非必需的对象。在内存足够时,不会回收;内存不足时,会将这些对象列入回收范围,若回收后还是内存不足,才会抛出 OOM 异常。
// 设置JVM参数 -Xmx32m -Xms32m 便于强迫GC
public static void main(String[] args) throws InterruptedException {
SoftReference sr = new SoftReference(new byte[1024 * 1024 * 16]); // 16M的匿名对象,软引用
System.out.println("gc前: " + sr.get()); // 对象存在
System.gc();
Thread.sleep(1000);
System.out.println("gc后,强迫gc前: " + sr.get()); // 对象依然存在
byte[] bytes = new byte[1024 * 1024 * 16]; // 创建16M的大对象,强迫GC
System.out.println("强迫gc后: " + sr.get()); // 对象为null,已被回收
}
软引用通常用在对内存敏感的程序中,如高速缓存等。内存够用就通过软引用取值,无需从繁忙的真实来源获取数据,以提升速度;内存不足就回收。Mybatis 中的缓存类 SoftCache 也用到了软引用:
public Object getObject(Object key) {
Object result = null;
SoftReference<Object> softReference = (SoftReference) this.delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
this.delegate.removeObject(key);
} else {
synchronized (this.hardLinksToAvoidGarbageCollection) {
this.hardLinksToAvoidGarbageCollection.addFirst(result);
if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) {
this.hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
3 弱引用
弱引用的使用和软引用类似,通过 java.lang.ref.WeakReference 类来实现。弱引用的特点是不管内存是否足够,只要发生 GC,弱引用的对象都会被回收。
public static void main(String[] args) throws InterruptedException {
WeakReference<Object> wr = new WeakReference<Object>(new Object()); // 弱引用
System.out.println("gc前: " + wr.get()); // 对象存在
System.gc();
Thread.sleep(1000);
System.out.println("gc后: " + wr.get()); // 对象为null
}
弱引用的应用:WeakHashMap 和 ThreadLocal
// 当key只有弱引用时,GC会清理键和值,WeakHashMap可作为简单的缓存表解决方案
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, String> map = new WeakHashMap<String, String>();
map.put(new String("key"), "value"); // 匿名对象key,弱引用
System.out.println("gc前: " + map); // map有数据
System.gc();
Thread.sleep(1000);
System.out.println("gc后: " + map); // map为空
}
// ThreadLocal.ThreadLocalMap.Entry继承了弱引用,key为当前线程实例,和WeakHashMap基本相同。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// ......
}
4 虚引用
虚引用不能获取对象实例,因此又被称为幻象引用,通过 java.lang.ref.PhantomReference 类来实现。虚引用的唯一目的就是在对象被GC回收时收到一个系统通知,可以配合 ReferenceQueue 来记录被回收的对象。如果虚引用的对象被垃圾回收,Java虚拟机就会把这个引用加入其关联的引用队列中。
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 引用队列
PhantomReference<Object> pr = new PhantomReference<>(new Object(), queue); // 虚引用
System.out.println(pr.get()); // null,虚引用不能获取对象
System.out.println(queue.poll()); // null,引用队列为空
System.gc();
Thread.sleep(1000);
// 若引用队列为空,remove()方法会阻塞,等待引用被放入引用队列后,才继续执行;poll()不会阻塞
// Reference<?> reference = queue.remove();
Reference<?> reference = queue.poll();
System.out.println(reference); // java.lang.ref.PhantomReference@73ad2d6,GC后,对象被回收,虚引用进入队列
}
在 JDK 1.4 中新加入的 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数,通过 Unsafe 类操作堆外内存(直接内存),在申请到一块直接内存后,会在Java 堆中分配一个 DirectByteBuffer 对象来保存这个直接内存的引用。这个 DirectByteBuffer 对象就用到了虚引用,当它被回收后,相应的用户线程会收到通知并对直接内存进行清理工作。这种方式避免了在 Java 堆和 Native 堆中来回复制数据,可以显著提高性能。
总结
引用类型 | 对象回收 | 生存时间 | 用途 |
强引用 | 从不 | JVM停止前 | 对象的一般状态 |
软引用 | 内存不足时 | 内存不足前 | 对象缓存 |
弱引用 | GC时 | 两次GC之间 | 对象缓存 |
虚引用 | GC时 | 两次GC之间 | 管理堆外内存 |