INDEX
§1 Reference
在 JDK 中,引用和对象一样,已经被抽象成了类,如下图
§2 各引用级别
强引用 FinalReference
- 强引用是最常见的引用,但 FinalReference 是非 public 的,因此不能如其他引用一样显示声明
- 对象赋值给一个引用型变量,此变量就是一个强引用
- 只要对象持有任意强引用,即可免疫 GC
但可能造成无用对象依然被强引用指向,所以是内存泄漏的主因
通常,对象超出引用作用于或显式置空(= null)就认为可以作为垃圾回收(还要参考垃圾回收策略)
强引用 Finalizer
-
Finalizer 是对 FinalReference 的实现,也是非 public 的
-
实现有
protected void finalize() throws Throwable { }
方法的对象会被识别为 Finalizer 对象 -
Finalizer 对象由 JVM 自动调用 register() 方法,完成创建,并在创建时将自己关联引用队列
-
Finalizer 通过
finalize()
定义了对象销毁时的自定义动作 -
Finalizer 对象至少需要两轮 GC 才能被回收,这是为了使被忘记释放资源的对象,即使真被忘了,也不会肯定不被回收
- 第一轮:通过
finalize()
将 Finalizer 对象的连接放到它的引用队列中,但因为 Finalizer 本身就是个强引用所以还没有被 GC - 第二轮:若 Finalizer 对象只持有 Finalizer 引用队列的强引用了,才会通过正常方式 GC
- 第一轮:通过
软引用 SoftReference
- 软引用可以通过下面方式声明
Reference<AAA> ra = new SoftReference<>(new AAA());
- 支持有软引用的对象在 JVM 内存充足时免疫 GC,但不充足时会被回收,如下例
- 内存不足 的界定
- 快要 OOM 前,JVM 触发 GC
- 执行后若依然要 OOM,认定内存不足,触发二次 GC,此时软引用纳入回收范围(下图中,可见两次 Full GC)
- 若执行后依然要 OOM,那就 OOM 了
- 可用于内存敏感型缓存
Map<String,SoftReference<CacheDdata>> cache = new HashMap<>();
public class SoftReferenceDemo {
public AAA b = new AAA();
private Reference<AAA> ra = new SoftReference<>(new AAA());
public Reference<AAA> getRa() {
return ra;
}
public static void main(String[] args) {
SoftReferenceDemo demo = new SoftReferenceDemo();
System.out.println("================================");
System.out.println(demo.b);
System.out.println(demo.getRa().get());
System.out.println("================================");
try {
int[] boom = new int[30*1024*1024];
}catch (Throwable e){
System.out.println("********************************");
System.out.println("boom !!!");
System.out.println("********************************");
}finally {
System.out.println("================================");
System.out.println(demo.b);
System.out.println(demo.getRa().get());
System.out.println("================================");
}
}
}
弱引用 WeakReference
- 弱引用可以通过下面方式声明
Reference<AAA> ra = new WeakReference<>(new AAA());
- 只要 GC 就会被回收
- 可以用来解决内存泄漏
Map<String,WeakReference> map = new HashMap<>();
或WeakHashMap
这种方式可以省略从容器中移除不需要的 key 的操作,以保证容器中的数据尽量齐全还不会导致内存泄漏
但注意 线程不安全,同时只能用于存储 相对无关紧要 的数据,否则一个 GC 就丢了
public class WeakReferenceDemo {
public AAA b = new AAA();
private Reference<AAA> ra = new WeakReference<>(new AAA());
public Reference<AAA> getRa() {
return ra;
}
public static void main(String[] args) {
WeakReferenceDemo demo = new WeakReferenceDemo();
System.out.println("================================");
System.out.println(demo.b);
System.out.println(demo.getRa().get());
System.out.println("================================");
System.gc();
System.out.println("================================");
System.out.println(demo.b);
System.out.println(demo.getRa().get());
System.out.println("================================");
}
}
WeakHashMap
WeakHashMap 是个 弱引用 版的 HashMap,其内部的 WeakHashMap.Entry 中 key 的类型为 WeakReference<Object>
这意味着,一旦 GC,只要 key 不被其他强引用指向(比如是在字符串,在常量池里),WeakHashMap 就地清空
见下面源码片段,和测试示例
// 此 Entry 非彼 Entry
Entry<K,V>[] table;
public WeakHashMap(int initialCapacity, float loadFactor) {
// 省略
table = newTable(capacity);
// 省略
}
// 此 Entry 中的 key 是 弱引用,所以一旦 GC,只要 key 不被其他强引用指向, WeakHashMap 就地清空
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
// 构造,注意 key 才是弱引用
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
//省略
}
简单的测试
虚引用 PhantomReference
- 虚引用,也称幽灵引用,是特殊引用
- 不能往往对象(虚引用的 get() 永远返回 null)
- 不影响对象的声明周期(不影响对象的 GC 抗性)
- 仅持有虚引用的对象等同于 无引用,随时可能被 GC
- 必须结合 ReferenceQueue 一起使用
- 用于跟踪对象被 GC 的状态,实现当对象 finalize 后触发做某些事的机制,以实现比 finalization 机制更灵活的回收操作
对象终止(finalization)机制:GC 对象前会先调用对象的 finalize() 方法,此方法允许重写。通常在这个方法中进行一些资源释放和清理的工作,比如关闭文件、套接字和数据库连接等
ReferenceQueue + WeakReference
默认的,如果 SoftReference、WeakReference、PhantomReference 的对象 在声明时指定了 引用队列,如下例,则被 GC 时会执行 finalize() 方法,对象销毁后,上述引用本身不在指向对象,但 对象会存入 引用队列
public void test() throws InterruptedException {
ReferenceQueue<AAA> rf = new ReferenceQueue<>();
Reference<AAA> weak = new WeakReference<>(new AAA(),rf);
System.out.println(weak.get());//base.learning.AAA@3cd1a2f1
System.out.println(rf.poll());//null
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println(weak.get());//null
System.out.println(rf.poll());//引用还在 java.lang.ref.WeakReference@2f0e140b
// System.out.println(rf.poll().get());//引用不指向对象了 jnull
}
ReferenceQueue + PhantomReference
public void test() throws InterruptedException {
ReferenceQueue<AAA> rf = new ReferenceQueue<>();
Reference<AAA> phantom = new PhantomReference<>(new AAA(),rf);
System.out.println(phantom.get());
System.out.println(rf.poll());
System.gc();
TimeUnit.SECONDS.sleep(1);
System.out.println(phantom.get());
System.out.println(rf.poll());
}
四大引用对比
四大引用的区别,主要集中在 对 GC 抵抗能力 上
强 | 软 | 弱 | 虚 | |
---|---|---|---|---|
对应类 | Reference | SoftReference | WeakReference | PhantomReference |
是否常见 | √ | × | × | × |
使用方式 | 常规场景都是 比如正常赋值 但不能如其他引用直接声明 | Reference<AAA> ra = new SoftReference<>(new AAA()); | 类似 SoftReference | 配合引用队列 new PhantomReference<>(new AAA(),rf); |
只持有此类引用的对象遇到垃圾回收时 | OOM 了都不收 | 内存不足时回收 | 只要 GC 就回收 | 随时回收 |
应用场景 | 遍地都是 | 内存敏感缓存(如 mybatis 中) | 解决容器不移除过期 key 导致的内存泄漏 WeakHashMap | 扩展 finalization 机制 |
ReferenceQueue
与 finalize()
的区别
finalize()
是 JVM 回收对象之前自动调用,即在回收之前生效ReferenceQueue
可以在对象被回收之后,额外保存其引用,即在回收之后生效