Java四种引用
- 强引用:有GC ROOT直接引用的对象,当没有再被GCROOT引用的时候,可以被垃圾回收
- 软引用:当被引用的对象只被软引用时,当发生垃圾回收且内存空间匮乏时会删除软引用所引用的对象,可以通过引用队列来释放引用自身
- 弱引用:当被引用的对象只被弱引用时,当发生垃圾回收时就会被回收弱引用所引用的对象,可以通过引用队列来释放引用自身
- 虚引用:一般在ByteBuffer开辟直接内存联系,当ByteBuffer本身被垃圾回收之后,但是对于其开辟的内存空间无法被回收(这是因为直接内存不属于JVM所管理的内存,而属于操作系统内存)。因此虚引用会记录该直接内存地址,并且在虚引用的内部会有一个Cleaner对象,当ByteBuffer被垃圾回收之后,虚引用一定会进入引用队列,而该队列会有一个ReferenceHandler线程去调用队列中对象的clean方法去释放直接内存。
引用队列
【引用队列 】用于将软引用和弱引用、虚引用的引用者放入该队列,当他们所引用的对象被垃圾回收之后,Reference引用关系就会进入该队列等待被线程回收。其中软引用和弱引用可以不配合引用队列使用,但是虚引用一定需要配合该队列。
实例程序
前提:为了测出效果,这里把堆内存的大小修改为20M,在启动参数中添加-Xmx20m;
- 强引用
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 0; i < 4; i++) {
list.add(new byte[1024 * 1024 * 5]);
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
说明:
这种是强引用的案例,我们日常工作中大多数对象都是被强引用。即GC Roots所能直接或间接的引用到的对象。这部分在GC时不会被回收,因为这些byte[]始终被生命周期更长的list引用着。因此当byte[]太多之后势必造成堆OOM。
- 软引用
public static void soft() {
List> lists = new ArrayList<>();
for (int i = 0; i < 5; i++) {
lists.add(new SoftReference<>(new byte[1024 * 1024 * 4]));
System.out.println("==============================");
for (SoftReference list : lists) {
System.out.println(list.get());
}
}
}
运行结果:
==============================
[B@682a0b20
==============================
[B@682a0b20
[B@3d075dc0
==============================
[B@682a0b20
[B@3d075dc0
[B@214c265e
==============================
[B@682a0b20
[B@3d075dc0
[B@214c265e
[B@448139f0
==============================
null
null
null
null
[B@7cca494b
说明:软引用在发生GC时,且堆内存空间不足时,就会释放被软引用所引用的对象。
- 弱引用
public static void weak() {
List> lists = new ArrayList<>();
for (int i = 0; i < 5; i++) {
lists.add(new WeakReference<>(new byte[1024*1024*4]));
System.out.println("========================");
for (WeakReference list : lists) {
System.out.println(list.get());
}
}
}
运行结果:
========================
[B@682a0b20
========================
[B@682a0b20
[B@3d075dc0
========================
[B@682a0b20
[B@3d075dc0
[B@214c265e
========================
null
null
null
[B@448139f0
========================
null
null
null
[B@448139f0
[B@7cca494b
说明:软引用在GC时,不管内存是否够用都会删除只被软引用所引用的对象。
- 虚引用
public static void fake() throws IOException {
Runnable runnable = () -> System.out.println("虚引用");
Object obj = new Object();
Cleaner.create(obj, runnable);
System.in.read();
obj = null;
System.gc();
System.in.read();
}
运行结果:
虚引用
说明:如上是一个虚引用的小Demo,也是ByteBuffer开辟直接内存的一个回收缩影。上面的核心类为Cleaner类,该类继承PhantomReference类,所以其本质就是一个虚引用。上述的create
方法就是使用虚引用来引用obj对象,当obj对象被垃圾释放时,引用关系会被放入到ReferenceQueue队列中,之后会有一个线程ReferenceHandler线程一直从Queue中取出Reference对象并调用clean方法,而clean方法就会调用create传入的第二个线程参数。
源码如下:
-
调用Cleaner.create方法时,会构造Cleaner对象,并将传入的Runnable赋值给全局变量thunk:
-
clean方法被调用时,会调用thunk线程的run执行用户自定义的释放动作:
-
Reference中有一个线程以最低优先级一直在运行:
-
如下是上述图片中的tryHandlePending方法,方法比较长,这里只截取一段,其中c是Cleaner的实例:
-
这里你可能有个疑问,就是那么多的Cleaner对象,他怎么知道调用哪个Cleaner对象的clean方法呢?
看来这一切都是这个pending的变量才是源头,看一下这个变量的定义吧,注释说的还是很清晰的:
回顾引用队列
在了解了上述背景之后,你可能想问。在软、弱引用。在被引用的对象在相应的场景被释放之后。那么这一层Reference引用关系是如何被清理的呢?仔细看一下下一段源码:
可以看到,ReferenceHandler线程会把垃圾回收器带来的Reference对象置空以下一次被垃圾回收(这个discovered就是Reference实例)。