我们知道,Java程序的运行时内存由JVM管理,负责回收、释放内存的程序是GC(Garbage Collection)。开发者无法手动释放某个特定实例并回收内存,只能释放引用并等待GC回收;或者手动触发GC:System.gc()
。
假设有如下代码:
1 | data class User(val name: String) |
当user变量被重新赋值时,原来的 A 实例将失去强引用,在下一次GC执行时,占用的内存区域将被回收。我们也可以手动触发GC来回收A的内存,如上述代码所示。
使用弱引用检测实例被回收
如果我们希望在后续代码中检测 A 实例是否被回收,可以使用弱引用(Weak Reference)。弱引用是JVM的四种引用类型之一,这四种分别是:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference),引用强度依次变弱。
- 强引用:只要存在强引用,对象将不会被回收。
- 软引用:如果只有软引用,在内存不足时可能被回收。
- 弱引用:弱引用不会影响对象的回收,如果没有更强的引用,对象将会被回收。
- 虚引用:虚引用不会影响对象的回收,甚至无法通过虚引用获取对象实例。
所以,使用弱引用检测实例是否被回收,可以使用弱引用。如下面代码所示:
1 | var user = User("User A") |
使用虚引用监控实例被回收
上述代码可以检测被弱引用引用的对象是否被GC回收,如果我们想在对象被回收时收到通知,就需要另一个工具:引用队列(Reference Queue)。在创建软引用、弱引用和虚引用时,我们都可以在构造方法中传入一个引用队列实例。当引用的对象将要被回收时,该引用会被添加到队列中,我们通过监听该队列是否为空,就可以实现检测对象是否将要被回收。
虽然这三种引用都可以实现相同的效果(三种引用都继承自 Reference ),但是软引用会影响GC逻辑,不应该使用。弱引用对比虚引用有获取引用实例的能力,而虚引用是专门为检测对象回收而设计的,所以推荐使用虚引用。(从实际效果来看,在这个场景中,弱引用和虚引用并没有明显区别)
参照这个原理,我们可以实现一个监听器来监控实例的回收,在实例即将被回收时收到回调。测试代码如下:
1 | val monitor = ReferenceRecyclingMonitor() |
一种 ReferenceRecyclingMonitor 的实现方式如下:
1 | class ReferenceRecyclingMonitor : Runnable { |