Java中弱引用指的是什么意思
创始人
2024-12-25 13:06:08
0次

  弱引用主要应用在不阻止它的key或者value 被回收的mapping。弱引用的出现就是为了垃圾回收服务的。它引用一个对象,但是并不阻止该对象被回收。如果使用一个强引用的话,只要该引用存在,那么被引用的对象是不能被回收的。弱引用则没有这个问题。在垃圾回收器运行的时候,如果一个对象的所有引用都是弱引用的话,该对象会被回收。

  弱引用案例深度解析

  理想的情况下,我们希望当我们不再使用一个对象的时候,能够在gc 发生的时候就把它回收掉。但是有些时候,由于我们的粗忽,在坏的情况下会导致内存溢出。这种案例尤其发生在一个生命使用周期很长的map 存放了很多实际使用生命周期短的对象。请看下面这个例子:

public class StrongRefenceDemo {
  static Map map;
  public static void main(String[] args) throws Exception {
  StrongRefenceDemo demo = new StrongRefenceDemo();
  demo.strongTest();
  System.out.println("gc 发生前:" + map.size());
  System.out.println("开始通知GC");
  //注意,这里只是通过垃圾回收器进行垃圾回收,并不一定马上执行
  System.gc();
  Thread.sleep(1000 * 5);
  System.out.println("gc 发生后:" + map.size());
  }
  /**
  * 强引用测试
  */
  public void strongTest() {
  map = new HashMap<>();
  String key = new String("key");
  String value = new String("value");
  map.put(key, value);
  key = null;
  value = null;
  }
  }

  运行后输出结果:

  gc 发生前:1 开始通知GC gc 发生后:1

  从输出的结果可以看到,即使我们通过把key和value 设置为null 来告诉jvm,我们不再使用这个对象了,map 里面对象依然没有被GC 回收,因为key和value 被一个强引用map 指向,根据可达性判断,垃圾回收器是不能回收掉key和value 这个对象的。map 被定义为statis 的静态变量,是一个使用生命周期很长的对象。在strongTest()方法中存在了一个key和value 的局部变量,它随着方法的执行完,这个变量的生命使用周期就结束了,但是粗糙的程序员忘记remove 了,这个时候垃圾回收器是不能回收它的。如果这种生命周期相对短的对象很多,最终就有可能消耗掉JVM中全部的内存。

  但是这里我有一个好奇,假如这里的key和value 指向的对象在执行完strongTest()方法 以后用不着了,但是我可能又不是很好的判断去主动调用remove 来移除它。想要垃圾回收器自己判断回收掉可不可以呢?答案其实是可以的,这个时候就是弱引用上场了,请看下面程序

public class WeakRefenceDemo {
  static Map, String> map;
  public static void main(String[] args) throws Exception {
  WeakRefenceDemo demo = new WeakRefenceDemo();
  demo.weakTest();
  System.out.println("gc 发生前:" + map.size());
  System.out.println("开始通知GC");
  //注意,这里只是通过垃圾回收器进行垃圾回收,并不一定马上执行
  System.gc();
  Thread.sleep(1000 * 5);
  System.out.println("gc 发生后:" + map.size());
  }
  /**
  * 若引用测试
  */
  public void weakTest() {
  map = new WeakHashMap<>();
  String key = new String("key");
  String value = new String("value");
  map.put(new WeakReference<>(key), value);
  key = null;
  value = null;
  }
  }

  运行上面代码输出结果

  gc 发生前:1 开始通知GC gc 发生后:0

  从输出结果0,我们可以判断已经成功被垃圾回收了。what?整个过程我们只是把HashMap 换成了WeakHashMap,并且key 由String 换成了WeakReference。其实就是由于字符串只有弱引用指向,所以可以被垃圾回收掉。是不是很简单,如果到这里你就停止研究弱引用了,那就太暴殄天物了

  WeakHashMap 深度解析

  上面的程序片段中,其实只有key 设置了为弱引用new WeakReference<>(key),那正常也就只有这个key 对应的内存被回收而已,由于没有调用remove ,里面的value 和entry 也是不会回收掉的,那为什么最后输出的size 是0 呢? 很好的问题,我们深入去看WeakHashMap 的源码,我们发现了一个神奇的方法expungeStaleEntries()。在看源码之前先解析下引用队列的概念: 在弱引用被回收的时候会把该对象放到引用队列中,也就意味着从引用队列中获取的对象都是被回收的对象,先解释到这里,足以满足我们下面的源码分析了,接下来会做详细的解析

/**
  * Expunges stale entries from the table.
  */
  private void expungeStaleEntries() {
  //这里从引用队列里面取出一个已经被回收的对象
  for (Object x; (x = queue.poll()) != null; ) {
  synchronized (queue) {
  @SuppressWarnings("unchecked")
  Entry e = (Entry) x;
  int i = indexFor(e.hash, table.length);
  Entry prev = table[i];
  Entry p = prev;
  //下面就是通过遍历链表来设置值为null 来告诉垃圾回收器回收掉
  //注意WeakHashMap 和HashMap 的数据结构都是通过数组+链表组成的,只有理解了这点才知道下面的代码做了什么
  while (p != null) {
  Entry next = p.next;
  //相等的时候,就意味着这个就是要回收的对象
  if (p == e) {
  //下面就是让回收对象不再被引用
  if (prev == e)
  table[i] = next;
  else
  prev.next = next;
  // Must not null out e.next;
  // stale entries may be in use by a HashIterator
  //这里通过设置value 为null 来告诉垃圾回收
  e.value = null; // Help GC
  size--;
  break;
  }
  prev = p;
  p = next;
  }
  }
  }
  }

  从上面的代码片段,大概的意思就是从引用队列里面取出被回收的对象,然后和WeakHashMap 中的对象查找,找到之后就把对应的value 也设置为null,并且把对应的entry 设置为null,来告诉GC 去回收它。从源码可以看到expungeStaleEntries() 这个方法在执行WeakHashMap中的任何方法的时候都会被调用到的

/**
  * Expunges stale entries from the table.
  */
  private void expungeStaleEntries() {
  //这里从引用队列里面取出一个已经被回收的对象
  for (Object x; (x = queue.poll()) != null; ) {
  synchronized (queue) {
  @SuppressWarnings("unchecked")
  Entry e = (Entry) x;
  int i = indexFor(e.hash, table.length);
  Entry prev = table[i];
  Entry p = prev;
  //下面就是通过遍历链表来设置值为null 来告诉垃圾回收器回收掉
  //注意WeakHashMap 和HashMap 的数据结构都是通过数组+链表组成的,只有理解了这点才知道下面的代码做了什么
  while (p != null) {
  Entry next = p.next;
  //相等的时候,就意味着这个就是要回收的对象
  if (p == e) {
  //下面就是让回收对象不再被引用
  if (prev == e)
  table[i] = next;
  else
  prev.next = next;
  // Must not null out e.next;
  // stale entries may be in use by a HashIterator
  //这里通过设置value 为null 来告诉垃圾回收
  e.value = null; // Help GC
  size--;
  break;
  }
  prev = p;
  p = next;
  }
  }
  }
  }

  到这里也就完全明白为什么value 不设置为弱引用和没有显性的调用remove 方法也可以回收掉了。

  引用队列

  从上面的的源码中,我们大概知道了引用队列的使用,那为什么要使用引用队列呢?假如没有引用队列,上面的例子我们就需要遍历全部的元素一个一个的去找,如果数量少那还好,如果数量多的时候,肯定就会出现一些性能问题。有了引用队列那就轻松可以解决上面的问题了。从WeakReference 源码中我们可以看到有两个构造函数,第二个是需要传入引用队列的

public WeakReference(T referent) {
  super(referent);
  }
  public WeakReference(T referent, ReferenceQueue q) {
  super(referent, q);
  }

  总结

  弱引用的出现是为了垃圾回收的

  一个对象只有弱引用指向它的时候,它是可以被回收的

  弱引用是在GC 发生的时候就进行回收,不管当时内存是否充足

  如果你在创建弱引用指定一个引用队列的话,弱引用对象被回收的时候,会把该对象放入引用队列中

  为了安全使用,每次都要判断下是否为空来判断该对象是否已经被回收,来避免空指针异常

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...