java 强、软、弱、虚引用

强引用

  • 除非显示指定创建软、弱、虚引用,其他应该都是强引用
  • 强引用为只要有任何一个强引用存在,被指向的对象就无法被 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,其中 keythreadlocal对象value 为对应的值
  • 当我们创建一个 threadlocal 对象,并在不同的线程中使用它存储值的时候,其实就是创建了 nEntry,分别存储在不同线程的 Map 中。在不同线程中使用则是从线程自己拥有的 Map 中根据 threadlocal 取对应的 value
  • 这里为什么要用弱引用呢?
    • 这是因为(k,v)如果不手动 remove,则其一直存在于线程的 map 中,这样即便我们将我们手中的 threadlocal 引用释放,map 中还存在着指向 threadlocal 的强引用,造成 threadlocal 无法被回收
    • 因此将 key 改为了弱引用,但此时 value 是同理,因此不手动 removevalue还存在泄露。但是 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) {
            // Paranoia
            return;
        }
      	// 通过分配堆外内存时保存的地址来释放堆外内存
        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 的方法,释放堆外内存

参考

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值