Java的五大引用
五大引用概述
基于可达性算法的垃圾回收
- 实线是强引用,虚线是其他引用
强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
- 我们平时用new出来的对象,用一个引用来指向这个对象,这个引用是强引用
软引用
- 通过GC Root对象强引用了我们的软引用对象,然后用软引用对象指向一个对象,这个对象就是被软引用指向的对象
- 软引用自身也是会占内存的
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象,可以配合引用队列来释放软引用自身
演示
/**
* 演示软引用
* -Xmx20m -XX:+PrintGCDetails -verbose:gc
*/
public class Demo2_3 {
private static final int _4MB = 4 * 1024 * 1024;
//这是我们的正常的进行创建对象,是强引用
public static void main(String[] args) throws IOException {
List<byte[]> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
list.add(new byte[_4MB]);
}
System.in.read();
}
//这是我们的软引用
public static void soft() {
// list --> SoftReference --> byte[]
List<SoftReference<byte[]>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
System.out.println("循环结束:" + list.size());
for (SoftReference<byte[]> ref : list) {
System.out.println(ref.get());
}
}
}
强引用执行的结果
- 会发生推内存溢出的现象,因为都是强引用,所以垃圾回收无法回收
软引用执行的结果
- 在第五次进行分配空间的时候,内存不够了,第一次进程垃圾回收发现内存还是不够,所以触发第二次的垃圾回收对软引用对象进行回收,所以发现前四个byte数组的内存地址为null
演示配合引用队列对软引用本身进行回收
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
/**
* 演示软引用, 配合引用队列
*/
public class Demo2_4 {
private static final int _4MB = 4 * 1024 * 1024;
public static void main(String[] args) {
List<SoftReference<byte[]>> list = new ArrayList<>();
// 引用队列
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for (int i = 0; i < 5; i++) {
// 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
System.out.println(ref.get());
list.add(ref);
System.out.println(list.size());
}
// 从队列中获取无用的 软引用对象,并移除
Reference<? extends byte[]> poll = queue.poll();
while( poll != null) {
list.remove(poll);
poll = queue.poll();
}
System.out.println("===========================");
for (SoftReference<byte[]> reference : list) {
System.out.println(reference.get());
}
}
}
- queue队列就是我们的引用队列,当我软引用所关联的byte数组对象被回收,软引用就会加到queue中
弱引用
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象,可以配合引用队列来释放弱引用自身
演示
public class Code_09_WeakReferenceTest {
public static void main(String[] args) {
// method1();
method2();
}
public static int _4MB = 4 * 1024 *1024;
// 演示 弱引用
public static void method1() {
List<WeakReference<byte[]>> list = new ArrayList<>();
for(int i = 0; i < 10; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB]);
list.add(weakReference);
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
System.out.println();
}
}
// 演示 弱引用搭配 引用队列
public static void method2() {
List<WeakReference<byte[]>> list = new ArrayList<>();
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
for(int i = 0; i < 9; i++) {
WeakReference<byte[]> weakReference = new WeakReference<>(new byte[_4MB], queue);
list.add(weakReference);
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
System.out.println();
}
System.out.println("===========================================");
Reference<? extends byte[]> poll = queue.poll();
while (poll != null) {
list.remove(poll);
poll = queue.poll();
}
for(WeakReference<byte[]> wake : list) {
System.out.print(wake.get() + ",");
}
}
}
弱引用执行的结果
虚引用
虚引用有个最重要的应用就是我们的直接内存分配的ByteBuffer
-
必须配合引用队列使用,在上的ByteBuffer对象中的cleaner对象就是我们的虚引用,存储的是我们的直接内存的地址,然后我们的ReferenceHandler线程调用cleaner对象的clean方法,然后调用了unsafe.freeMemory来释放我们的直接内存
-
因为直接内存不受JVM的内存自动管理的控制,所以通过这些方法来间接的实现对直接内存的回收
终结器引用
- finallize()方法是我们的Object中的方法,所以所有对象都有这个方法
- 重写了了finalize方法,A4对象第一次垃圾回收的时候并不是立马被回收,因为重写了finallize方法,所以虚拟机会自动创建一个终结器引用,执行A4对象的第一次回收的时候,会将终结器引用放入引用队列(不会回收A4对象)
- 由我们的Finalizer线程通过终结器引用找到被引用的对象,并调用他的finallize方法,第二次GC时才会回收这个A4对象
- 不推荐使用
- 因为处理引用队列的线程优先级很低,被处理的机会很少,可能造成这个对象的finallize()迟迟不被调用,导致这块内存迟迟不被回收