1. 程式人生 > >java四種引用及在LeakCanery中應用

java四種引用及在LeakCanery中應用

java 四種引用

Java4種引用的級別由高到低依次為:

StrongReference  >  SoftReference  >  WeakReference  >  PhantomReference

1. StrongReference

String tag = new String("T");   

此處的 tag 引用就稱之為強引用。而強引用有以下特徵:

1. 強引用可以直接訪問目標物件。
2. 強引用所指向的物件在任何時候都不會被系統回收。
3. 強引用可能導致記憶體洩漏。

我們要討論的其它三種Reference較之於強引用而言都屬於“弱引用”,也就是他們所引用的物件只要沒有強引用,就會根據條件被JVM的垃圾回收器所回收,它們被回收的時機以及用法各不相同。下面分別來進行討論。

2. SoftReference

軟引用有以下特徵:

1. 軟引用使用 get() 方法取得物件的強引用從而訪問目標物件。
2. 軟引用所指向的物件按照 JVM 的使用情況(Heap 記憶體是否臨近閾值)來決定是否回收。
3. 軟引用可以避免 Heap 記憶體不足所導致的異常。

當垃圾回收器決定對其回收時,會先清空它的 SoftReference,也就是說 SoftReference 的 get() 方法將會返回 null,然後再呼叫物件的 finalize() 方法,並在下一輪 GC 中對其真正進行回收。

3. WeakReference

WeakReference 是弱於 SoftReference 的引用型別。弱引用的特性和基本與軟引用相似,區別就在於弱引用所指向的物件只要進行系統垃圾回收,不管記憶體使用情況如何,永遠對其進行回收(get() 方法返回 null)。

弱引用有以下特徵:

1. 弱引用使用 get() 方法取得物件的強引用從而訪問目標物件。
2. 一旦系統記憶體回收,無論記憶體是否緊張,弱引用指向的物件都會被回收。
3. 弱引用也可以避免 Heap 記憶體不足所導致的異常。

4. PhantomReference(虛引用)

PhantomReference 是所有“弱引用”中最弱的引用型別。不同於軟引用和弱引用,虛引用無法通過get()方法來取得目標物件的強引用從而使用目標物件,觀察原始碼可以發現 get() 被重寫為永遠返回 null。

虛引用有以下特徵:

虛引用永遠無法使用 get() 方法取得物件的強引用從而訪問目標物件。
虛引用所指向的物件在被系統記憶體回收前,虛引用自身會被放入 ReferenceQueue 物件中從而跟蹤物件垃圾回收。
虛引用不會根據記憶體情況自動回收目標物件。
虛引用必須和引用佇列(ReferenceQueue)聯合使用

Reference與ReferenceQueue 使用demo

定義一個物件Brain

public class Brain  {

    public int mIndex;
    // 佔用較多記憶體,當系統記憶體不足時,會自動進行回收
    private byte []mem;

    public Brain(int index) {
        mIndex = index;
        mem = new byte[1024 * 1024];
    }
    
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        LogUtils.e("Brain", "finalize + index=" + mIndex);
    }
}

建立Reference並新增到RefrenceQueue中

// 定義幾個成員變數
ReferenceQueue<Brain> mWeakQueue = new ReferenceQueue<>();
ReferenceQueue<Brain> mPhQueue = new ReferenceQueue<>();

List<WeakReference<Brain>> mWeakList = new ArrayList<>();
List<PhantomReference<Brain>> mPhList = new ArrayList<>();


// 開啟守護執行緒用於檢測ReferenceQue中是否有物件被新增
private void startDemoThread() {
    Thread threadWeak = new Thread(() -> {
            try {
                int cnt = 0;
                WeakReference<Brain> k;
                // remove 方法為阻塞式的, 而poll方法不是
                while((k = (WeakReference) mWeakQueue.remove()) != null) {
                    LogUtils.e(TAG, "回收了WeakReference指向物件, : cnt=" + (cnt++) + " wf=" + k);
                }
            } catch(InterruptedException e) {
                //結束迴圈
            }
        }, "MainActivityWeak");
        threadWeak.setDaemon(true);
        threadWeak.start();

        Thread threadPh = new Thread(() -> {
            try {
                int cnt = 0;
                PhantomReference<Brain> k;
                while((k = (PhantomReference) mPhQueue.remove()) != null) {
                    LogUtils.e(TAG, "回收了PhantomReference指向物件, cnt=" + (cnt++) + "   pr=" + k);
                }
            } catch(InterruptedException e) {
                //結束迴圈
            }
        }, "MainActivityPhantom");
        threadPh.setDaemon(true);
        threadPh.start();
}
 
// 注意建立的Reference物件需要暫存起來(實測中,如果Reference被回收是不會被新增到ReferenceQueue中的)
private void test() {
    for (int i=0; i<1000; i++) {
                    Brain src1 = new Brain(i);
                    Brain src2 = new Brain(100000 + i);
                    // 將Reference註冊到RefrenceQueue中
                    WeakReference<Brain> weakReference = new WeakReference<Brain>(src1, mWeakQueue);
                    mWeakList.add(weakReference);
                    
                    //將Reference註冊到RefrenceQueue中
                    PhantomReference<Brain> phantomReference = new PhantomReference<>(src2, mPhQueue);
                    mPhList.add(phantomReference);


                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
}
    

結果列印:

2019-01-29 19:22:27.499 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=0 wf=java.lang.ref.WeakReference@e1f904c
2019-01-29 19:22:27.499 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=1 wf=java.lang.ref.WeakReference@82fc895
2019-01-29 19:22:27.500 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=2 wf=java.lang.ref.WeakReference@3b3fdaa
2019-01-29 19:22:27.500 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=3 wf=java.lang.ref.WeakReference@668fd9b
2019-01-29 19:22:27.501 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=0
2019-01-29 19:22:27.501 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100000
2019-01-29 19:22:27.502 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=4 wf=java.lang.ref.WeakReference@8db6538
2019-01-29 19:22:27.502 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=5 wf=java.lang.ref.WeakReference@f915911
2019-01-29 19:22:27.503 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=1
2019-01-29 19:22:27.503 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100001
2019-01-29 19:22:27.504 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=2
2019-01-29 19:22:27.505 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100002
2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=3
2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100003
2019-01-29 19:22:27.507 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=4
2019-01-29 19:22:27.508 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100004
2019-01-29 19:22:27.508 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=5
2019-01-29 19:22:27.509 9283-9292/com.selftest.test.testapp3 E/IFLY_SDK_Brain: finalize + index=100005
2019-01-29 19:22:27.629 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=0   pr=null
2019-01-29 19:22:27.629 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=1   pr=null
2019-01-29 19:22:27.629 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=6 wf=java.lang.ref.WeakReference@e2c4a76
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=2   pr=null
2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=7 wf=java.lang.ref.WeakReference@4cfd877
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=3   pr=null
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=4   pr=null
2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=8 wf=java.lang.ref.WeakReference@37d9ce4
2019-01-29 19:22:27.630 9283-9309/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了PhantomReference: cnt=5   pr=null
2019-01-29 19:22:27.630 9283-9308/com.selftest.test.testapp3 E/IFLY_SDK_MainActivity: 回收了WeakReference: cnt=9 wf=java.lang.ref.WeakReference@ea1754d

結果分析:

  1. 當物件被回收後,持有他的引用WeakReference/PhantomReference會被放入ReferenceQueue中
  2. WeakReference在原始物件回收之前被放入ReferenceQueue中,而PhantomReference是在回收之後放入ReferenceQueue中

WeakReference在Leakcanery中的應用

LeakCanery是Android檢測記憶體洩漏的工具,可以檢測到Activity/Fragment存在的記憶體洩漏。

檢測原理:

  1. 在Application中註冊監聽所有Activity生命週期的listener,registerActivityLifecycleCallbacks。
//ActivityRefWatcher 中的程式碼
public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }
  1. 當Activity的onDestroy被呼叫時,生成一個uuid,標記這個Activity的WeakReference。
  2. 建立一個弱引用,並與一個跟蹤所有activit回收的ReferenceQueue相關聯。(放入一個map中,key : uuid, value:weakReference)
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };

具體的watch執行如下:

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
  }

ensureGoneAsync執行如下:

// watchExecutor 在一定時間後檢查被註冊的WeakReference有沒有被新增到ReferenceQueue中
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
  1. 在onDestry被呼叫後若干秒執行如下操作:到ReferenceQueue中去取這個Activity,如果能夠取到說明這個Activity被正常回收了。如果無法回收,觸發GC,再去RerenceQueue中取如果還是無法取到,說明Activity沒有被系統回收,可能存在記憶體洩漏。
    真正核心的程式碼如下:
long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    // 如果ReferenceQue中有activity的弱引用,則將retainedKeys中的uuid移除
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    
    // 如果activity對應的uuid已經被移除,說明activity已經被回收,無記憶體洩漏
    if (gone(reference)) {
      return DONE;
    }
    
    // 觸發gc,進行垃圾回收
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    
    // 如果uuid還沒有被移除,說明activiy存在記憶體洩漏,需要dump記憶體,進行分析
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

HeapDump dump記憶體和分析的過程這裡就不細說