强引用
java默认的引用方式就是强引用,只要栈帧没有被弹出并且引用还指向着对象,对象就不会被 GC 清除,通过把引用置为 null ,可以使得 GC 清除对象
软引用
在出现内存不足的情况下会被 GC 回收,这里注意不是出现 GC 就会回收,而是出现内存不足才会回收
//JVM 参数设置 -XX:+PrintGCDetails -Xms10m -Xmx10m
public static void main(String[] args) {
SoftReference<byte[]> sr = new SoftReference<byte[]>(new byte[1024*1024*2]);
ArrayList<byte[]> arrayList = new ArrayList<byte[]>();
for(int i = 1; i <= 10; i++) {
arrayList.add(new byte[1024*1024]);
System.out.println("第" + i + "次创建对象,弱引用对象:" + sr.get());
}
}
代码中创建了一个 2mb 的 byte 数组
在第 6 次先 ArrayList 中添加对象时,由于内存空间不够发生了 GC ,GC 完毕后可以看到内存占用由 7766k 变成了 5693k 从减少了 2073k ,稍稍大于 2mb,可以确定 byte 数组被清除了,再打印对象为null
弱引用
只要出现了 GC ,即无论是否出现内存不足,对象都会被回收
public static void main(String[] args) {
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
System.out.println("before GC : " + weakReference.get());
System.gc();
System.out.println("after GC : " + weakReference.get());
}
我们显式调用 GC 后被对象被清除
注意要是用下面方式使用弱引用,就起不到弱引用的效果
public static void main(String[] args) {
byte[] bytes = new byte[1024*1024*10];
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes);
System.out.println("before GC : " + weakReference.get());
System.gc();
System.out.println("after GC : " + weakReference.get());
}
因为还存在其他引用指向对象(软引用也如此)
虚引用
虚引用和没有引用一样,只不过在 GC 回收对象时,会将对象添加到一个队列中,可以让程序观察到对象被回收了
用处,在 NIO 中存在一个 DirectByteBuffer 类,该类使用的是堆外内存,由于内存不在 JVM 堆空间中,如果 DirectByteBuffer 对象不在指向该空间,且不回收堆外空间就会出现内存泄漏的情况,所以需要使用一个机制来通知 JVM 把堆外空间给回收了(马士兵视频中的例子)
PhantomReference 需要与 ReferenceQueue 配合使用,当 PhantomReference 关联的对象被回收时, PhantomReference 就会被添加到 ReferenceQueue 队列中
下图中例子模拟了当对象被回收时通知到程序进行后续工作
package cn.arc.java;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;
/**
* @author arc3102
* @date 2021/5/5 18:50
*/
public class PhantomReferenceTest {
private static final List<byte[]> LIST = new LinkedList<byte[]>();
private static final ReferenceQueue<byte[]> QUEUE = new ReferenceQueue<byte[]>();
public static void main(String[] args) {
final PhantomReference<byte[]> phantomReference = new PhantomReference<byte[]>(new byte[1024 * 1024], QUEUE);
new Thread(new Runnable() {
public void run() {
while(true) {
LIST.add(new byte[1024*1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}
}).start();
new Thread(new Runnable() {
public void run() {
while(true) {
Reference<? extends byte[]> poll = QUEUE.poll();
if(poll != null) {
System.out.println("--- 虚引用对象被 JVM 回收了 --- " + poll);
}
}
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们把 byte 数组改为对象,重写 finalize 方法,再跑一下
package cn.arc.java;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;
/**
* @author arc3102
* @date 2021/5/5 22:30
*/
public class PhantomReferenceTest2 {
static class M{
private byte[] bytes;
M() {
this.bytes = new byte[1024*1024];
}
@Override
protected void finalize() throws Throwable {
System.out.println("我是M的finalize()");
super.finalize();
}
}
private static final List<byte[]> LIST = new LinkedList<byte[]>();
private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<M>();
public static void main(String[] args) {
final PhantomReference<M> phantomReference = new PhantomReference<M>(new M(), QUEUE);
new Thread(new Runnable() {
public void run() {
while(true) {
LIST.add(new byte[1024*1024]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}
}).start();
new Thread(new Runnable() {
public void run() {
while(true) {
Reference<? extends M> poll = QUEUE.poll();
if(poll != null) {
System.out.println("--- 虚引用对象被 JVM 回收了 --- " + poll);
}
}
}
}).start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
从结果中可以看出在对象的 finalize 方法完成后 phantomreference 才会被添加到队列中,而且两者相隔的时间还不短