Java四种引用类型
一 什么是引用
Object obj = new Object();
如图所示 , Java中变量存放在栈 , 对象存放在堆 , 变量的值实际是指向对象在堆内存中的初始地址 , 所以我们也称这块堆内存存在着引用 .
接下来看这段代码:
new Object();
请问, 该对象存在引用吗?答案是否. 因为在栈中(或其他地方)不存在该对象的地址 , 则该对象不存在被引用
再看一段代码:
List list = new ArrayList();
list.add(new Object());
请问: new 出来的Object对象存在引用吗? 答案是存在 , 因为该对象被list所引用
在对引用有一个简单大概的了解之后 , 接下来我们来看看Java具体存在着哪些引用类型.
二 Java的四种引用类型
1)强引用
Object obj = new Object();
Java默认就是强引用 . 强引用的作用是: GC时JVM永远不会回收强引用对象 , 即使内存不足(会直接抛出OOM Error: Java heap space) ; 如果要回收强引用对象 , 则必须先断开引用,即设置obj为null
obj = null;
然后再等待下一次GC时回收该对象 .
For example:
public class Test2 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 1; i <= 10; i++) {
System.out.print("当前是第"+i+"次添加:");
byte[] bytes = new byte[1024 * 1024];// 往list中添加一个1m的对象,添加十次
list.add(bytes);
System.out.println("添加成功");
}
}
}
我们设置jvm启动参数(设置堆启动内存以及最大内存都为5m)
-Xms5M -Xmx5M
查看执行结果
当前是第1次添加:添加成功
当前是第2次添加:添加成功
当前是第3次添加:添加成功
当前是第4次添加:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.ibm.cn.se.reference.Test2.main(Test2.java:17)
分析执行结果😦分析之前, 我们得先知道IDEA在启动程序时, 会最起码占用1.5m左右的内存)
for循环执行10次, 每次往list中添加一个1m的对象 , 前3次都添加成功 , 共添加了3m,再加上idea的启动内存一共4.5m左右 ; 当第四次再添加1m时, 装不下了(4.5m+1m>5m), 此时的容量已经超过了我们设置的堆最大内存(5m), 但是jvm并不会回收强引用对象,所以 jvm抛出了错误(OOM ERROR : java heap space)
结论: jvm无论内存足够与否GC时都不会回收强引用对象
2)软引用
软引用需要用到SoftRefercence类, 与强引用的区别是:
- 内存足够时: 强引用对象和软引用对象都不会被回收
- 内存不足时: 软引用对象会被回收, 而强引用对象不会
强引用对象 | 软引用对象 | |
---|---|---|
内存足够 | 不会回收 | 不会回收 |
内存不足 | 不会回收 | 会被回收 |
For example:
public class Test3 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 1; i <= 10; i++) {
System.out.print("当前是第"+i+"次添加:");
byte[] bytes = new byte[1024 * 1024];// 往list中添加一个1m的对象,添加十次
SoftReference softReference = new SoftReference<>(bytes);
list.add(softReference);
System.out.println("添加成功");
// 打印list当前存在的对象
for (int j = 0; j < list.size(); j++) {
System.out.println(" 列表下标:"+j+"所对应的值:"+((SoftReference)list.get(j)).get());
}
}
}
}
同样的设置jvm启动参数
-Xms5M -Xmx5M
执行程序 , 查看执行结果
当前是第1次添加:添加成功
列表下标:0所对应的值:[B@33c7353a
当前是第2次添加:添加成功
列表下标:0所对应的值:[B@33c7353a
列表下标:1所对应的值:[B@681a9515
当前是第3次添加:添加成功
列表下标:0所对应的值:[B@33c7353a
列表下标:1所对应的值:[B@681a9515
列表下标:2所对应的值:[B@3af49f1c
当前是第4次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:[B@19469ea2
当前是第5次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:[B@19469ea2
列表下标:4所对应的值:[B@13221655
当前是第6次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:[B@19469ea2
列表下标:4所对应的值:[B@13221655
列表下标:5所对应的值:[B@2f2c9b19
当前是第7次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:null
列表下标:4所对应的值:null
列表下标:5所对应的值:null
列表下标:6所对应的值:[B@31befd9f
当前是第8次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:null
列表下标:4所对应的值:null
列表下标:5所对应的值:null
列表下标:6所对应的值:[B@31befd9f
列表下标:7所对应的值:[B@1c20c684
当前是第9次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:null
列表下标:4所对应的值:null
列表下标:5所对应的值:null
列表下标:6所对应的值:[B@31befd9f
列表下标:7所对应的值:[B@1c20c684
列表下标:8所对应的值:[B@1fb3ebeb
当前是第10次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:null
列表下标:4所对应的值:null
列表下标:5所对应的值:null
列表下标:6所对应的值:null
列表下标:7所对应的值:null
列表下标:8所对应的值:null
列表下标:9所对应的值:[B@548c4f57
Process finished with exit code 0
分析结果(IDEA启动内存占用大小约1.5m左右):
当前三次添加后,堆内存都能装的下; 当第四次添加时, 剩余堆空间为0.5m(5m-4.5m) , 不足以放下一个新的对象 , 所以jvm进行了一次GC , 回收掉软引用对象(list下标为0,1,2的对象) , 所以下标0,1,2所指向的值为null ;
当前是第4次添加:添加成功
列表下标:0所对应的值:null
列表下标:1所对应的值:null
列表下标:2所对应的值:null
列表下标:3所对应的值:[B@19469ea2
GC完毕之后, 堆内存又有空间了 , 此时往list添加一个对象成功(下标为3). 后续执行结果以此类推.
结论:
当堆空间内存足够时 , jvm不会回收掉软引用对象; 但是一旦内存不足时, 则会进行GC, 回收掉软引用对象
3)弱引用
软引用需要用到WeakReference 类, 与强引用, 软引用的区别是:
- 内存足够时: 强引用对象和软引用对象都不会被回收 , 但弱引用对象会被回收
- 内存不足时: 软引用对象和弱引用对象会被回收, 而强引用不会
简而言之: 只要进行GC, 弱引用对象就一定会被回收
强引用对象 | 软引用对象 | 弱引用对象 | |
---|---|---|---|
内存足够 | 不会回收 | 不会回收 | 会被回收 |
内存不足 | 不会回收 | 会被回收 | 会被回收 |
For example:
public class Test4 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 1; i <= 3; i++) {
System.out.print("当前是第"+i+"次添加:");
byte[] bytes = new byte[1024 * 1024];// 往list中添加一个1m的对象,添加三次
// 第一次添加强引用对象
if(i == 1){
list.add(bytes);
}else if(i == 2){
// 第二次添加软引用对象
SoftReference softReference = new SoftReference<>(bytes);
list.add(softReference);
}else if(i == 3){
// 第三次添加弱引用对象
WeakReference weakReference = new WeakReference<>(bytes);
list.add(weakReference);
}
System.out.println("添加成功");
}
System.out.println("添加完毕,查看列表:");
printList(list);
System.gc();
System.out.println("GC一次后再次查看列表");
printList(list);
}
private static void printList(List list ){
// 打印list当前存在的对象
System.out.println(" 列表下标0所对应的值:"+list.get(0));
System.out.println(" 列表下标1所对应的值:"+((SoftReference)list.get(1)).get());
System.out.println(" 列表下标2所对应的值:"+((WeakReference)list.get(2)).get());
}
}
同样的设置jvm启动参数
-Xms5M -Xmx5M
执行程序 , 查看执行结果
分析结果:
我们给list循环三次添加1m的对象 :
- 第一次添加了强引用对象(java默认就是强引用)
- 第二次添加了软引用对象
- 第三次添加了弱引用对象
添加了三个对象之后, 当前堆内存空间大小=1.5m+3m<5m , 所以并未触发GC , 此时我们查看列表发现三个下标都有数据;
当我们手动调用 System.gc()后,发现下标为2的弱引用对象被回收了, 而下标为0的强引用对象和下标为1的软引用对象并未被回收
结论:
jvm无论内存足够与否GC时都一定会回收弱引用对象
4)虚引用
虚引用(phantomReference)是一种特殊的引用类型 , 这种类型在实例化时与其他类型不同 , 需要传入ReferenceQueue参数,
ReferenceQueue QUEUE = new ReferenceQueue<>();
PhantomReference reference = new PhantomReference<>(new Object() , QUEUE);
且调用get()方法获取到的永远是一个null对象
System.out.println(reference.get());
以下是我找资料时截取的一段话,供大伙参考
虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
原文链接:https://blog.csdn.net/qq_39192827/article/details/85611873