学习该部分的知识,我的初衷只是为了学习ThrealLocal,结果却被迫补课。后来了解到,这部分知识在面试中出现的频率还比较高,所以就单独写一篇博客,记录记录,也希望对你有帮助
文章目录
- 1、四种引用类型
- 2、四种类型的测试
- 2.1、强引用 new
- 2.2、软引用 SoftReference
- 2.3、弱引用 WeakReference
- 2.4、虚引用 PhantomReference
- 3、总结
1、四种引用类型
- 强引用:最普通的引用 Object o = new Object()
- 软引用:垃圾回收器, 内存不够的时候回收 (缓存)
- 弱引用:垃圾回收器看见就会回收 (防止内存泄漏)
- 虚引用:垃圾回收器看见二话不说就回收,跟没有一样 (管理堆外内存) DirectByteBuffer -> 应用到NIO Netty
2、四种类型的测试
finalize()方法:当对象被回收时, finalize()方法会被调用, 但是不推荐使用去回收一些资源,因为不知道他什么时候会被调用, 有时候不一定会调用
public class Gc {
@Override
//用于我们显式的调用gc方法以后,测试打印结果
protected void finalize() throws Throwable {
System.out.println("finalize");
}
}
2.1、强引用 new
正常引用,但没有人指向的时候就会被回收.
测试代码:
public class RefStypeTest01 {
public static void main(String[] args) throws IOException {
Gc gc = new Gc();
//第一次执行gc,由于我们的gc对象存在强引用,所以不会被垃圾清理
System.gc();
System.out.println("--");
gc = null;
//由于我们将gc对象赋值为null,所以我们会打印相应的结果
System.gc();
System.in.read();
}
}
执行结果:
--
finalize
2.2、软引用 SoftReference
内存不够的时候回收
我们将我们的堆内存修改为19M
测试代码:
package pers.mobian.ThreadLocal;
import java.io.IOException;
import java.lang.ref.SoftReference;
public class RefStypeTest01 {
public static void main(String[] args) throws IOException {
//设置我们的软引用的空间 10M
SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);
//打印我们的对象信息
System.out.println(softReference.get());
//gc回收
System.gc();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(softReference.get());
//再分配一个数组,由于heap内存放不下, 这个时候系统会回收一次, 如果还是不够,就会回收软引用
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
}
}
执行结果:
[B@880ec60
[B@880ec60
null
2.3、弱引用 WeakReference
使用GC后就会被回收
测试代码:
public class RefStypeTest02 {
public static void main(String[] args) throws IOException {
WeakReference<Gc> weakReference = new WeakReference<>(new Gc());
//打印还没有被回收前的情况
System.out.println(weakReference.get());
//使用gc回收
System.gc();
//再次打印被回收以后的情况
System.out.println(weakReference.get());
}
}
执行结果:
pers.mobian.ThreadLocal.Gc@3f3afe78
null
finalize
2.4、虚引用 PhantomReference
JVM无法访问直接清理
测试代码:
public class RefStypeTest03 {
private static final List<Object> LIST = new LinkedList<>();
private static final ReferenceQueue QUEUE = new ReferenceQueue();
public static void main(String[] args) throws IOException {
//实例化我们的虚引用
PhantomReference<Gc> phantomReference = new PhantomReference<>(new Gc(), QUEUE);
new Thread(() -> {
while (true) {
LIST.add(new byte[1024 * 1024]);
//我们不断地添加数据,但是却拿不到值,因为虚引用的内容在JVM外面
System.out.println(phantomReference.get());
}
}).start();
new Thread(() -> {
while (true) {
//添加一个存储信息的Queue
Reference<? extends Gc> poll = QUEUE.poll();
//当该判断成立时,说明poll队列中含有了我们的对应的清理堆外内存的信息
if (poll != null) {
System.out.println("-----虚引用对象被JVm回收了--------" + poll);
return;
}
}
}).start();
}
}
执行结果:
...
null
null//加满我们的堆内存后,才会出发我们的GC,继而向Queue里面添加数据
-----虚引用对象被JVm回收了--------java.lang.ref.PhantomReference@76cbab9f
null
null
null
...
总结:我们传统的数据交换方式,比如获取网络上(BIO、NIO)的数据。我们在网卡这一步首先将数据加载到内存,再将内存终得数据复制到JVM里面,然后再复制到堆里面。这样的十分的浪费资源。于是就出现了我们的虚引用,它直接由OS管理。当我们的不需要使用该数据以后,由于该数据所在的空间是在JVM外面,JVM无法直接处理,于是我们就在堆内存的引用里面添加了一个Queue队列,Queue用来存储信息,当我们不需要使用虚引用的数据时,就会向Queue队列里面添加信息,当Queue检测到有对应地数据时,就会去进行相应的清理堆外内存的操作。继而解决了GC回收不了数据信息,引发的内存溢出。
3、总结
强引用:正常的使用方式。
软引用:内存不够的时候,进行清理。可用于网络上的
弱引用:没有引用时遇到GC直接进行清理。在我们的ThreadLocalMap中就使用了该方式
虚引用:无法获取到对应的值,向Queue队列中添加数据来间接清理。主要是为了增强数据的传输效率