在java中对象的引用有强、软、弱、虚四种,这些引用级别的区别主要体现在对象的生命周期、回收时机的不同。这里在已知结论的情况下对其进行验证。
准备工作
1. 设置内存
为方便调试,将内存设置为16MB
-
依次点击菜单栏的Run—>Edit Configurations
-
点击 Modify options —> Add VM option
-
添加参数
我这里设置的内存是16m, 对应参数为 -Xms16m -Xmx16m
2. 内存检测
使用 runtime.freeMemory() 的api,用来获取到未使用内存大小
强引用
这是最常见的引用,就是平时用的"=" 赋值,当将变量指向null时则表示去除了强引用,当触发gc时变量会被回收。
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
runtime.gc();
System.out.println("创建数组前剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
byte[] bytes = new byte[10 * 1024 * 1024];
System.out.println("创建数组后所用内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
// 去除强引用
bytes = null;
System.gc();
System.out.println("垃圾回收之后所用内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
}
打印结果:
创建数组前剩余内存:14.730506896972656MB
创建数组后所用内存:3.5946426391601562MB
垃圾回收之后所用内存:14.682281494140625MB
可以看出当变量bytes指向null后,调用gc将会回收byte数组。但当bytes=null后,就无法引导到byte数组。
软引用
软引用引用的变量在gc时不会被回收,只有在内存不足时才会被回收。
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
runtime.gc();
System.out.println("创建数组前剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
byte[] bytes = new byte[10 * 1024 * 1024];
System.out.println("创建数组后所用内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
SoftReference<byte[]> softReference = new SoftReference<>(bytes);
System.out.println("添加软引用后剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
bytes = null;
System.out.println("弱引用的引用对象:" + softReference.get());
System.gc();
System.out.println("去除强引用后剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
System.out.println("弱引用的引用对象:" + softReference.get());
// 创建一个大于剩余内存的数组
byte[] bigBytes = new byte[(int) (runtime.freeMemory() + 1024)];
System.out.println("弱引用的引用对象:" + softReference.get());
}
打印结果:
创建数组前剩余内存:14.630073547363281MB
创建数组后所用内存:3.5899581909179688MB
添加软引用后剩余内存:3.5699386596679688MB
软引用的引用对象:[B@5305068a
去除强引用后剩余内存:3.66766357421875MB
软引用的引用对象:[B@5305068a
软引用的引用对象:null
当bytes = null并且gc时,byte[]并没有被回收。但在bigBytes时,由于内存不足,jvm会对软引用的对象进行回收。
弱引用
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
runtime.gc();
System.out.println("创建数组前剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
byte[] bytes = new byte[10 * 1024 * 1024];
System.out.println("创建数组后所用内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
WeakReference<byte[]> weakReference = new WeakReference<>(bytes);
System.out.println("添加弱引用后剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
bytes = null;
System.out.println("弱引用的引用对象:" + weakReference.get());
System.gc();
System.out.println("弱引用的引用对象:" + weakReference.get());
}
}
打印结果:
创建数组前剩余内存:14.629707336425781MB
创建数组后所用内存:3.5896987915039062MB
添加弱引用后剩余内存:3.5696792602539062MB
弱引用的引用对象:[B@5305068a
弱引用的引用对象:null
弱引用在gc触发后就会回收对象
虚引用
虚引用的创建与软引用、弱引用不同,它需要传入一个引用队列
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
runtime.gc();
System.out.println("创建数组前剩余内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
byte[] bytes = new byte[10 * 1024 * 1024];
System.out.println("创建数组后所用内存:" + runtime.freeMemory() / 1024.0 / 1024 + "MB");
ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
PhantomReference<byte[]> phantomReference = new PhantomReference<>(bytes, referenceQueue);
bytes = null;
new Thread(() -> {
while (true) {
PhantomReference<byte[]> poll = (PhantomReference<byte[]>) referenceQueue.poll();
if (poll != null) {
System.out.println("虚引用对象被回收了");
break;
}
}
}).start();
System.out.println("gc前虚引用的引用对象:" + phantomReference.get());
System.gc();
System.out.println("gc后虚引用的引用对象:" + phantomReference.get());
}
输出:
创建数组前剩余内存:14.629470825195312MB
创建数组后所用内存:3.5897750854492188MB
gc前虚引用的引用对象:null
gc后虚引用的引用对象:null
虚引用对象被回收了:java.lang.ref.PhantomReference@618defd6
不能通过虚引用去引用对象,在gc后可以在ReferenceQueue.poll()得到虚引用
总结
创建 | 赋值null | gc | 内存不足gc | ||
---|---|---|---|---|---|
强引用 | 使用 | ✅ | ❎ | ❎ | ❎ |
存活 | ✅ | ✅ | ❎ | ❎ | |
软引用 | 使用 | ✅ | ✅ | ✅ | ❎ |
存活 | ✅ | ✅ | ✅ | ❎ | |
弱引用 | 使用 | ✅ | ✅ | ❎ | ❎ |
存活 | ✅ | ✅ | ❎ | ❎ | |
虚引用 | 使用 | ❎ | ❎ | ❎ | ❎ |
存活 | ✅ | ✅ | ❎ | ❎ |