文章目录
- 强引用
- 软引用
- 弱引用
- 虚引用
- 参考
强引用
- 除非显示指定创建软、弱、虚引用,其他应该都是强引用
- 强引用为只要有任何一个强引用存在,被指向的对象就无法被 gc
强引用1 -------| |----> object
强引用2 -------|只要任何一个强引用存在,object 就无法被回收
软引用
- 弱引用通常用于索引可有可无的对象,典型场景:缓存
- 弱引用当 jvm 内存不够时,会将只有弱引用指向对象回收
强引用 ----> 弱引用对象 ----> object此时如果内存不够,由于 object 只有一个弱引用指向,会将 object 释放
当时弱引用对象不会释放,因为有一个强引用指向它示例:
SoftReference<byte[]> m = new SoftReference<>(new byte[1024 * 1024 * 10]);m 为强引用,指向 SoftReference 对象,该对象在堆上,但本身表示弱引用,指向一个 byte 数组
弱引用
- 弱引用为若一个对象只有弱引用指向,则其不管内存是否足够,gc时都会被回收
- 因此主要用于资源的自动回收
- 例如我们将某项资源存放在缓存中,同时我们持有一个表示该资源生命周期的强引用,当我们想释放该资源时,只需要将强引用释放掉,该资源就会被回收,而不会产生内存泄露
强引用 ----> 弱引用对象 ----> object,发生 gc 时 object 都会被回收示例:
String str = new String();
WeakReference<String> m = new WeakReference<>(str);
强引用 str ---------------------------||----> object
强引用 m ----> 弱引用对象 weakObj ------|当我们把 str 释放掉之后,object 会被 gc
- 使用场景:目前已知为
theadlocal
(但好像意义并不大) threadlocal
的实现原理为:每个线程都会创建一个Map
用于存储key、value
,其中key
为threadlocal对象
,value
为对应的值- 当我们创建一个
threadlocal
对象,并在不同的线程中使用它存储值的时候,其实就是创建了n
个Entry
,分别存储在不同线程的Map
中。在不同线程中使用则是从线程自己拥有的Map
中根据threadlocal
取对应的value
- 这里为什么要用弱引用呢?
- 这是因为
(k,v)
如果不手动remove
,则其一直存在于线程的map
中,这样即便我们将我们手中的threadlocal
引用释放,map
中还存在着指向threadlocal
的强引用,造成threadlocal
无法被回收 - 因此将
key
改为了弱引用,但此时value
是同理,因此不手动remove
,value
还存在泄露。但是value
不能改为弱引用,因为这样会造成我们访问的时候,得到一个null
- 所以感觉用在 threadlocal 中意义不大,但可以借鉴
- 这是因为
threadlocal 不使用弱引用的场景
强引用 tl -------------------------||------> threadlocal 对象
强引用 thread.map[n].key ----------|
此时,即便我们手中的 tl 置为 null,threadlocal 对象依然不会被回收(但我们可能以为回收了,造成内存泄露)因此 threadlocal 自身实现中,将 key 改为弱引用:强引用 tl -------------------------||------> threadlocal 对象
弱引用 thread.map[n].key ----------|
此时,我们将 tl 置为 null,threadlocal 对象只有一个弱引用指向的情况下会被自动回收
虚引用
- 功能和弱引用类似,只是弱引用作用于堆上的对象且弱引用指向的对象的回收逻辑是固化的(固定为 jvm 将该对象释放)
- 虚引用类似,只有虚引用指向对象会由 jvm 执行将其关联对象的回调
- 因此虚引用类似于一个钩子函数,绑定在某个资源上 (这个资源可以是堆内存也可以是堆外内存),当该资源没有强引用关联时 (类似于没人用了),触发虚引用这个钩子,实现内存自动释放
强引用 -------||---->object
虚引用 -------|当强引用消失后,只有虚引用指向该对象,触发虚引用绑定对象的对象方法,对 object 进行清理操作
示例:
- DirectByteBuffer
// 创建堆外内存
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);}DirectByteBuffer(int cap) {// ....try {// 使用unsafe对象分配一块堆外内存base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}// 【重点】创建一个虚引用对象,这个虚引用对象指向this对象(也就是指向创建的byteBuffer对象)// 同时将Deallocator保存到了虚引用对象cleaner上。当虚引用对象被放入队列后,就会执行Deallocator对象的clean() 方法来清除堆外内存。看下面源码体现// 可以看到这里将分配的堆外内存地址传递给了Deallocator对象保存。到时候释放堆外内存的时候也就要依靠这个地址cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;}Deallocator的释放逻辑:
private static class Deallocator implements Runnable{// 当虚引用对象被放入队列后,JVM中会有一个线程去取这个队列中的元素。然后就会执行这个方法释放内存public void run() {if (address == 0) {// Paranoiareturn;}// 通过分配堆外内存时保存的地址来释放堆外内存unsafe.freeMemory(address);address = 0;Bits.unreserveMemory(size, capacity);}}============== 整体的示意图 ===========强引用 bytebuf ------| (申请)|---> ByteBuffer------------|| |---> 堆外内存
虚引用 cleaner ------| || (清理) ||-----------------> Dealloactor -----------|(cleaner中的成员强引用)
因此,当 bytebuf 置为 null,强引用消失,ByteBuffer 对象只有一个虚引用指向。
jvm 将 cleaner 对象放入 Reference queue,
jvm 线程从 queue 中取 Cleaner 对象,执行其 clean 方法,
clean 中调用 Dealloactor 的方法,释放堆外内存
参考
- https://blog.csdn.net/Hellowenpan/article/details/121264731