天天看點

WeakReference(弱引用)與WeakHashMapWeakReference(弱引用)與WeakHashMap

WeakReference(弱引用)與WeakHashMap

是什麼?

Java中4種引用,強度依次是(S > S >W > P)

  • 強引用
    java中建立對象預設是強引用,隻要引用被持有,不會被GC
  • 軟引用(SoftReference)
    隻有記憶體不足的時候才會被GC
  • 弱引用(WeakReference)
    隻要發生GC,對象就會被銷毀
  • 虛引用(PhantomReference)
    形同虛設,永遠get不到該對象

舉例

  • 軟引用
/**
   * -verbose:gc -Xmx150M 軟引用使用 get() 方法取得對象的強引用進而通路目标對象。 所指向的對象按照JVM的使用情況(Heap 記憶體是否臨近門檻值)來決定是否回收。
   * 可以避免 Heap 記憶體不足所導緻的異常。
   */
  static class Soft {

    public static void main(String[] args) throws Exception {
      SoftReference<byte[]> reference = new SoftReference<>(new byte[ *  * ]);
      System.out.println("gc by user");
      System.gc();  //gc 不會釋放記憶體 隻有在OutOfMemoryError之前釋放
      TimeUnit.MILLISECONDS.sleep();
      System.out.println("after gc size is " + reference.get().length);
      byte[] tmp = new byte[ *  * ];
      System.out.println("memory is full, can't release any more, size is " + reference.get());
    }
  }
           

執行結果:

gc by user
[GC (System.gc())  K->K(K),  secs]
[Full GC (System.gc())  K->K(K),  secs]
after gc size is 
[GC (Allocation Failure)  K->K(K),  secs]
[GC (Allocation Failure)  K->K(K),  secs]
[Full GC (Allocation Failure)  K->K(K),  secs]
[GC (Allocation Failure)  K->K(K),  secs]
[Full GC (Allocation Failure)  K->K(K),  secs]
memory is full, can't release any more, size is null
           
  • 弱引用
//-Xmx20M -XX:+PrintGCDetails
  private static void test0() {
    WeakHashMap<Integer, byte[]> d = new WeakHashMap<>();
    for (int i = ; i < ; i++) {
      System.out.println(i);
      //下面兩行差別?
      //int i存在棧中不會被gc,new Integer(i)存放在堆中
      //d.put(i, new byte[1024 * 1024]);//value 導緻的OOM
      d.put(new Integer(i), new byte[ * ]);//正常進行
    }
    System.out.println(d.size());
  }
           
  • 虛引用
/**
   * -verbose:gc -Xmx150M
   * 虛引用
   * 永遠無法使用 get() 方法取得對象的強引用進而通路目标對象。
   * 所指向的對象在被系統記憶體回收前,虛引用自身會被放入ReferenceQueue對象中進而跟蹤對象垃圾回收。
   * 不會根據記憶體情況自動回收目标對象。
   */
  static class Phantom {

    public static void main(String[] args) {
      ReferenceQueue<Object> refQueue = new ReferenceQueue<>();
      PhantomReference<Object> referent = new PhantomReference<>(new Object(), refQueue);
      PhantomReference<Object> referent2 = new PhantomReference<>(new Object(), refQueue);
      System.out.println("referent get" + referent.get());// null
      System.out.println("referent2 get" + referent2.get());// null
      System.gc();
//      System.runFinalization();
      System.out.println("referent=>" + referent);
      System.out.println("referent2=>" + referent2);
      System.out.println("poll=>" + refQueue.poll());
      System.out.println("poll=>" + refQueue.poll());
      System.out.println("poll=>" + refQueue.poll());
    }
  }
           

執行結果:

referent getnull
referent2 getnull
[GC (System.gc())  K->K(K),  secs]
[Full GC (System.gc())  K->K(K),  secs]
referent=>java.lang.ref.PhantomReference@28a418fc
referent2=>java.lang.ref.PhantomReference@5305068a
poll=>java.lang.ref.PhantomReference@28a418fc
poll=>java.lang.ref.PhantomReference@5305068a
poll=>null
           

在Java中使用場景

  • 軟引用(SoftReference)
    cache對象需要在即将發生OOM的時候騰出空間,防止OOM;JDK中Class類中reflectionData就是用了軟引用
  • 弱引用(WeakReference)

JDK中WeakHashMap,例子如下:

public class WeakHashMapTest {
  static class Key {
    String id;
    public Key(String id) {
      this.id = id;
    }
    public String toString() {
      return id;
    }
    public int hashCode() {
      return id.hashCode();
    }
    public boolean equals(Object r) {
      return (r instanceof Key)
          && id.equals(((Key) r).id);
    }
    public void finalize() {
      System.out.println("Finalizing Key " + id);
    }
  }

  static class Value {
    String id;
    public Value(String id) {
      this.id = id;
    }
    public String toString() {
      return id;
    }
    public void finalize() {
      System.out.println("Finalizing Value " + id);
    }
  }

  public static void main(String[] args) throws Exception {
    int size = ;
    if (args.length > ) {
      size = Integer.parseInt(args[]);
    }
    Key[] keys = new Key[size];
    WeakHashMap<Key, Value> whm = new WeakHashMap<Key, Value>();
    for (int i = ; i < size; i++) {
      Key k = new Key(Integer.toString(i));
      Value v = new Value(Integer.toString(i));
      if (i %  == ) {
        keys[i] = k;
      }
      whm.put(k, v);
    }
    System.out.printf("before gc WeakHashMap are %s.%n", whm.toString());
    System.gc();
    Thread.sleep();  //把處理器的時間讓給垃圾回收器進行垃圾回收
    System.out.printf("keys are %s.%n", Arrays.toString(keys));
    System.out.printf("after gc WeakHashMap are %s.%n", whm.toString());
  }
}
           

執行結果

before gc WeakHashMap are {=, =, =, =, =, =, =, =, =, =}.
Finalizing Key 
Finalizing Key 
Finalizing Key 
Finalizing Key 
Finalizing Key 
Finalizing Key 
keys are [, null, null, , null, null, , null, null, ].
after gc WeakHashMap are {=, =, =, =}.
           

原理:

WeakHashMap有一個成員變量

Entry<K,V>[] table

, 類似于HashMap中的table,隻是Entry中的key是弱引用

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

,

上面解釋過,弱引用就是在每次GC都會清除該對象,key被清除之後,value是強引用,該怎麼辦?清理key時把key放在

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

, 大部分方法都會調用

expungeStaleEntries

,該方法主要作用就是清除弱引用key對應的value.

是以WeakHashMap的使用場景就是短時間cache的對象,當然這個也是有缺點的,假設剛計算出來的對象碰巧遇到gc,立刻又被回收了,

下次使用又要重新計算;有人說你可以先把這個應用先加入強引用類似于上面的WeakHashMapTest,那麼什麼時候釋放呢?

如果你已經知道确切的釋放時間,那麼還用WeakReference有什麼意義呢...

WeakHashMap為什麼把key作為弱引用而非value?(2018.3.6)

用來處理當key被因為弱引用gc回收時map将删除kv,是以用作cache不是很合理,被用來儲存一些對象的中繼資料,當這些對象有生命周

期,不需要你人為控制,比如Thread,當線程活的時候你直接可以使用該thread對應的value,一旦銷毀之後,map自動回收value.

代碼如下:

MapMaker guava提供更多樣的選擇

public class WeakHashMapTest {
  public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
      try {
        Thread.sleep();
      } catch (InterruptedException ignore) {
      }
    });
    t1.start();
    WeakHashMap<Thread, String> map = new WeakHashMap<>();
    map.put(t1, "metadata");
    t1 = null;
    try {
      Thread.sleep();
    } catch (InterruptedException ignore) {
    }
    System.out.println(map);
    System.gc();
    System.out.println(map);
  }
}
           
  • 虛引用(PhantomReference)

    虛引用必須和引用隊列(ReferenceQueue)聯合使用才有意義,一定程度上它與finalize起到的作用大緻相同,都在對象被gc回

    收之前做一些收尾工作. 如何使用見利用 PhantomReference 替代 finalize.

finalize如何使用,借助dubbo裡面的源碼:

private final Object finalizerguardian = new Object() {
        @Override
        protected void finalize() throws Throwable {
            super.finalize();

            if (!ReferenceConfig.this.destroyed) {
                logger.warn("ReferenceConfig(" + url + ") is not DESTROYED when FINALIZE");

                /* 先不做Destroy操作
                try {
                    ReferenceConfig.this.destroy();
                } catch (Throwable t) {
                        logger.warn("Unexpected err when destroy invoker of ReferenceConfig(" + url + ") in finalize method!", t);
                }
                */
            }
        }
    };
           

參考:

java中的4種reference的差别和使用場景(含理論、代碼和執行結果)

Effective Java Item7:Avoid Finalizers,解釋為什麼finalize是不安全的,不建議使用

繼續閱讀