微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

JVM面试(七)-强引用、软引用、弱引用、虚引用及应用场景

强引用、软引用、弱引用、虚引用及应用场景

引用类型的作用

  • 可以 通过代码的方式 决定 某些对象的生命周期
  • 有利于JVM进行垃圾回收

强引用

Java中最常见的就是强引用
一个对象 赋给 一个引用变量 时,这个引用变量 就是一个强引用
有强引用的对象 一定为 可达性状态,所以不会被垃圾回收机制回收

强引用是造成Java内存泄漏的主要原因
如果想中断 强引用 和 某个对象 之间的关联关系,可以 显示地 将引用 赋值为 null,JVM就会在合适的时间回收该对象,例如 集合类中的clear方法,这里会牵扯到内存泄漏和内存溢出的区别

软引用-SoftReference

软引用通过SoftReference类实现
如果 一个对象 只有 软引用,则 在系统内存空间不足时 该对象 将被回收
垃圾回收器没有回收之前,该对象都可以被程序使用

SoftReference的特点 是 它的一个实例 保存 对一个Java对象 的 软引用,该软引用的存在 不妨碍 垃圾收集线程 对 该Java对象 的回收
也就是说,一旦SoftReference 保存了 对一个Java对象 的 软引用后,在垃圾线程 对 这个Java对象 回收前,SoftReference类所提供的get()方法 返回Java对象 的 强引用

一旦 垃圾线程 回收了 该Java对象之后,get()方法将返回null

软可及对象 的 清理 是由 垃圾收集线程 根据其特定算法 按照 内存需求决定的

在创建软引用时,还可以传入ReferenceQueue(引用队列),当 JVM 回收 某个软引用对象 之后 会将 该SoftReference对象 添加进 这个队列,因此就可以知道 这个对象 啥时候被回收了,可以做一些想做的操作

    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

使用SoftReference做缓存

软引用在实际中有重要的应用,例如浏览器的后退按钮,这个后退时显示的网页内容可以重新进行请求或者从缓存中取出:

  • 如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建
  • 如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出这时候就可以使用软引用

使用SoftReference作为缓存

import java.lang.ref.softReference;
import java.util.HashMap;

/**
 * SoftRefenceCache
 * @param <K> key的类型.
 * @param <V> value的类型.
 */
public class SoftReferenceCache<K, V> {
  private final HashMap<K, SoftReference<V>> mCache;

  public SoftReferenceCache() {
    mCache = new HashMap<K, SoftReference<V>>();
  }

  /**
   * 将对象放进缓存中,这个对象可以在GC发生时被回收
   * 
   * @param key key的值.
   * @param value value的值型.
   */

  public void put(K key, V value) {
    mCache.put(key, new SoftReference<V>(value));
  }

  /**
   * 从缓存中获取value
   * 
   * @param key
   *
   * @return 如果找到的话返回value,如果被回收或者压根儿没有就返* 回null
   */
   
  public V get(K key) {
    V value = null;

    SoftReference<V> reference = mCache.get(key);

    if (reference != null) {
      value = reference.get();
    }

    return value;
  }
}

弱引用-WeakReference

WeakReference是Java语言规范中 为了区别 直接的对象引用(程序中 通过构造函数 声明出来的对象引用)而定义的 另外一种引用关系
WeakReference标志性的特点是:reference实例(WeakReference对象) 不会影响到 被应用对象(T) 的 GC回收行为
即 只要 对象(T) 被 除WeakReference对象之外 所有的对象 解除引用 后,该对象(T) 便可以被GC回收
只不过 在对象(T) 被回收之后,reference实例(WeakReference对象) 想获得 被应用的对象(T)时 程序会返回null
这一段很关键!!!

弱引用通过WeakReference类实现
如果 一个对象 只有弱引用,则垃圾回收过程中 一定会被回收(无论内存是否充足,这是弱引用和软引用的区别!!!)
如果 存在强引用 同时与之关联,则在 进行垃圾回收时 也不会回收该对象(软引用也是如此)

弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果 弱引用 所引用的对象 被JVM回收,这个弱引用 就会被加入到 与之关联的 引用队列中

应用场景:
如果一个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么应该用 Weak Reference 来记住此对象
或者想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候就应该用弱引用,这个引用不会在对象的垃圾回收判断中产生任何附加的影响

面试题:ThreadLocal为什么使用WeakReference?

ThreadLocal#getMap方法 返回 当前线程t的threadLocals变量
threadLocals的类型是 ThreadLocal类 的 静态内部类ThreadLocalmap

ThreadLocal.ThreadLocalmap threadLocals = null;

ThreadLocalmap一个比较特殊的Map,它的每个Entry的key都是一个弱引用
Entry声明为WeakReference,岂不是说,在某个任意时刻,GC都有可能把ThreadLocal对象对应的Value也给回收了?
因为 声明的WeakReference引用 指向的是 ThreadLocal对象,而value则是强引用类型

ThreadLocalmap 和 普通map的最大区别 就是 它的Entry 是针对 ThreadLocal弱引用
即当ThreadLocal 没有 其他引用为空时,JVM就可以GC回收ThreadLocal,从而得到一个null的key


static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    //key就是一个弱引用
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

当threadLocal实例 可以被GC回收时,系统可以检测到 该threadLoca l对应的 Entry 是否已经过期(根据reference.get() == null来判断,如果为true则表示过期,程序内部称为stale slots)来自动做一些清除工作
否则如果不做清除工作的话 容易产生 内存无法释放的问题,因为Entry.value对应的对象 即使不再使用,但由于被threadLocalmap所引用 导致 无法被GC回收
实际代码中,ThreadLocalmap会在set,get方法调用rehash方法,rehash方法调用resize方法resize方法针对Entry中key=null的情况把对应的value也设置为null, set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalmap中所有的过期slots
当然将threadLocal对象设置为null 并不能完全避免 内存泄露对象,最安全的办法仍然是调用ThreadLocal的remove方法,来彻底避免可能的内存泄露

阅读参考:

虚引用-Phantomreference

虚引用通过Phantomreference类实现
虚引用 必须和 引用队列联合使用,主要用于 跟踪对象 的 垃圾回收状态

如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收
虚引用 主要用来跟踪 对象 被垃圾回收的活动

垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把 这个虚引用 加入到 与之关联的引用队列中
程序可以通过 判断引用队列中 是否已经加入了虚引用,来了解 被引用的对象 是否将要被垃圾回收
如果程序发现 某个虚引用 已经被加入到 引用队列,那么 就可以 在所引用的对象 的 内存 被回收之前 采取必要的行动
Phantomreference#get()方法永远返回空,不管对象有没有被回收

应用场景:
可以用来跟踪对象呗垃圾回收的活动
一般可以通过虚引用 达到 回收一些 非java内的一些资源 比如堆外内存的行为
例如:在 DirectByteBuffer 中,会创建一个Phantomreference的子类Cleaner的虚引用实例 用来引用 该DirectByteBuffer 实例
Cleaner创建时会添加一个Runnable实例,当 被引用的DirectByteBuffer对象 不可达 被垃圾回收时,将会执行Cleaner实例内部的Runnable实例的run方法,用来回收堆外资源

引用队列-ReferenceQueue

引用队列,在检测到适当的可到达性更改后,垃圾回收器 将 已注册的引用对象(Reference) 添加到 该队列中

如果在创建一个引用(Reference)对象时,指定了ReferenceQueue,那么当 引用对象(Reference) 指向的对象 达到 合适的状态(根据引用类型不同而不同)时,GC 会把 引用对象本身(Reference) 添加到这个队列中,方便处理它
因为引用对象(Reference) 指向的对象 GC会自动清理,但是 引用对象本身 也是对象(是对象就占用一定资源),所以需要自己清理

总结

引用类型被回收时间用途生存时间
强引用never对象的一般状态jvm停止运行时
软引用内存不足时对象缓存直到内存不足时
弱引用gc时对象缓存直到gc时
虚引用unkNowunkNowunkNow

阅读参考:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐