Java 知识-- Java 中的几种引用

本文最后更新于:2 个月前

强引用

  • 最常用的引用方式,垃圾收集器宁愿抛出 OOM 也不愿意回收它引用对象

  • 与其它三种引用不同,强引用创建的强引用对象就是创建的对象本身,而其他都会创建对应的软/弱/虚对象后,再去引用实际对象。

软引用 (SoftReference)

  • 如果内存空间不足了,垃圾收集器就会回收相应对象的内存。

  • 可以通过 get() 方法获取引用的实际对象

  • 使用场景:实现内存敏感的高速缓存。

软引用可以和一个引用队列( ReferenceQueue )联合使用,当软引用对象指向的对象已经被被回收时(非这个软引用对象自身),JVM 就会把这个软引用(这个软引用自身)加入到与之关联的引用队列中。

弱引用 (WeakReference)

  • 垃圾收集器一旦发现了弱引用对象就会把他回收

同软引用一样,弱引用也可以和一个引用队列一起使用

虚引用 (PhantomReference)

  • 与其他几种引用不同,虚引用不会决定对象的生命周期。如果一个对象被虚引用持有,那么它就和没有任何引用一样,随时可能被垃圾收集器回收。

  • 虚引用和软/弱引用的区别:

    官方注释

    Phantom reference objects, which are enqueued after the collector determines that their referents may otherwise be reclaimed. Phantom references are most often used for scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism.

    虚拟引用对象,在收集器确定其引用对象可以通过其他方式回收之后,将其排队。与 Java finalize 机制相比,虚引用最常用于以更灵活的方式用事前清理操作。

    If the garbage collector determines at a certain point in time that the referent of a phantom reference is phantom reachable, then at that time or at some later time it will enqueue the reference.

    如果垃圾收集器在某个时间点确定幻像引用的引用对象是虚引用是可达的,则在那时或以后的某个时间,它将使该引用入队。

    In order to ensure that a reclaimable object remains so, the referent of a phantom reference may not be retrieved: The get method of a phantom reference always returns null.

    为了确保保留可回收对象,可能不会检索幻像引用的引用对象:幻像引用的get方法始终返回null。

    Unlike soft and weak references, phantom references are not automatically cleared by the garbage collector as they are enqueued. An object that is reachable via phantom references will remain so until all such references are cleared or themselves become unreachable.

    与软引用和弱引用不同,幻象引用在排队时不会被垃圾收集器自动清除。通过幻像引用可访问的对象将保持不变,直到清除所有此类引用或它们自身无法访问为止。

    总结:

    1. 不会主动回收对象

    2. get 永远为 null

  • JDK 的应用

    JDK 中直接内存的回收就用到虚引用。

    由于 JVM 自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),所以直接内存的分配和回收都是由 Unsafe 类去操作。

    Java 在申请一块直接内存之后,在堆上再生成一个 DirectByteBuffer 对象存储直接内存的地址,其内部有一个静态类 Deallocator 实现了 Runnable 接口,并维护一个对 DirectByteBuffer 这个对象的虚引用。

    当 DirectByteBuffer 对象被 GC 回收时,Deallocator 会通过虚引用得到通知,创建一个线程释放该 DirectByteBuffer 对象 malloc 申请的直接内存空间。

    • 为什么使用 DirectByteBuffer

      1. 减少复制操作,加快传输速度

        HotSpot虚拟机中,GC除了CMS算法之外,都需要移动对象。

        在NIO实现中(如: FileChannel.read(ByteBuffer dst), FileChannel.write(ByteByffer src)), 底层要求连续的内存,且使用期间不得变动, 如果提供的 Buffer 是 HeapByteBuffer,为了保证在数据传输时,被传输的 byte 数组背后的对象不会被 GC 回收或者移动,JVM 会首先将堆中的 byte 数组拷贝至直接内存,再由直接内存进行传输。

        那么,相比于 HeapByteBuffer 在堆上分配空间,直接只用 DirectByteBuffer 在直接内存分配就节省了一次拷贝,加快了数据传输的速度。

      2. 减少GC压力

        虽然 GC 仍然管理 DirectByteBuffer,但基于 DirectByteBuffer 分配的空间不属于 GC 管理,如果 IO 数量较大,可以明显降低 GC 压力。

参考博客

Java Doc

虚引用在 NIO 中的应用