LeakCanary 與 鵝場(chǎng)Matrix ResourceCanary對(duì)比分析

LeakCanary是Square公司基于MAT開源的一個(gè)內(nèi)存泄漏檢測(cè)神器,在發(fā)生內(nèi)存泄漏的時(shí)候LeakCanary會(huì)自動(dòng)顯示泄漏信息,現(xiàn)在更新了好幾個(gè)版本,用kotlin語言重新實(shí)現(xiàn)了一遍;鵝場(chǎng)APM性能監(jiān)控框架也集成了內(nèi)存泄露模塊 ResourcePlugin ,這里就兩者進(jìn)行對(duì)比。

1、組件啟動(dòng)

LeakCanary自動(dòng)注冊(cè)啟動(dòng)

原理:專門定制了一個(gè)ContentProvider,來注冊(cè)啟動(dòng)LeakCanary

實(shí)現(xiàn)如下:

/**
 * Content providers are loaded before the application class is created. [LeakSentryInstaller] is
 * used to install [leaksentry.LeakSentry] on application start.
 */
internal class LeakSentryInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    CanaryLog.logger = DefaultCanaryLog()
    val application = context!!.applicationContext as Application
    InternalLeakSentry.install(application)
    return true
  }
  
  ...
}

ResourcePlugin 需要手動(dòng)啟動(dòng)

public class MatrixApplication extends Application {
    ...
    @Override
    public void onCreate() {
        super.onCreate();
        ...
        ResourcePlugin resPlugin = null;
        if (matrixEnable) {
           resPlugin = new ResourcePlugin(new ResourceConfig.Builder()
                    .dynamicConfig(dynamicConfig)
                    .setDumpHprof(false)
                    .setDetectDebuger(true)     //only set true when in sample, not in your app
                    .build())
            //resource
            builder.plugin(resPlugin );
            ResourcePlugin.activityLeakFixer(this);

           ...
        }

        Matrix.init(builder.build());
        if(resPlugin != null){
            resPlugin.start(); 
        }

    }
  
}

2、watcher范圍和自動(dòng)wacherd的對(duì)象

LeakCanary RefWatcher可以watcher任何對(duì)象(包括Activity、Fragment、Fragment.View)

class RefWatcher{
    fun watch(watchedInstance: Any) {...}
    fun watch( watchedInstance: Any,name: String) {...}
}

支持自動(dòng)watcher Activity、Fragment、Fragment.View對(duì)象

1.自動(dòng)watcher Activity

internal class ActivityDestroyWatcher {
private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
            refWatcher.watch(activity)
        }
      }
    }

  companion object {
    fun install(... ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(refWatcher, configProvider) 
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

ActivityDestroyWatcher.install在LeakSentryInstaller.onCreate間接調(diào)用,注冊(cè)ActivityLifecycleCallbacks 監(jiān)聽Activity的生命周期,從而實(shí)現(xiàn)自動(dòng)watcher Activity對(duì)象。

2.自動(dòng)watcher Fragment、Fragment.View

//子類有
//SupportFragmentDestroyWatcher
//AndroidOFragmentDestroyWatcher
internal interface FragmentDestroyWatcher {

  fun watchFragments(activity: Activity)

  companion object {
    ...
    fun install(... ) {
    
     ...
      application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
        override fun onActivityCreated( activity: Activity,
savedInstanceState: Bundle? ) {
          for (watcher in fragmentDestroyWatchers) {
            watcher.watchFragments(activity)
          }
        }
      })
    }
 
  }
}

FragmentDestroyWatcher .install在LeakSentryInstaller.onCreate間接調(diào)用,注冊(cè)ActivityLifecycleCallbacks 監(jiān)聽Activity的生命周期函數(shù)onCreate,然后對(duì)activity.fragmentManager注冊(cè)FragmentLifecycleCallbacks監(jiān)聽Fragment的周期函數(shù),從而實(shí)現(xiàn)自動(dòng)watcher Fragment、Fragment.View如下:

internal class XXXFragmentDestroyWatcher(...) : FragmentDestroyWatcher {

  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
         //watcher view
         refWatcher.watch(view)
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        //watcher fragment
        refWatcher.watch(fragment)
      }
    }
  }
  

  //AndroidOFragmentDestroyWatcher
  override fun watchFragments(activity: Activity) {
    val fragmentManager = activity.fragmentManager
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
  }
  
 //SupportFragmentDestroyWatcher
  override fun watchFragments(activity: Activity) {
    if (activity is FragmentActivity) {
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
    }
  }
}

Replugin 只有一個(gè)ActivityRefWatcher,只支持watcher Activity,也是通過注冊(cè)ActivityLifecycleCallbacks 監(jiān)聽Activity的生命周期,從而實(shí)現(xiàn)自動(dòng)watcher Activity對(duì)象。

public class ActivityRefWatcher extends FilePublisher implements Watcher {
     @Override
    public void start() {
        stopDetect();
        final Application app = mResourcePlugin.getApplication();
        if (app != null) {
            app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);
            //輪詢檢測(cè)是否發(fā)生溢出
            scheduleDetectProcedure();
           
        }
    }
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {  
   @Override    public void onActivityDestroyed(Activity activity) {    
    //push mDestroyedActivityInfos集合中,通過輪詢檢測(cè)對(duì)mDestroyedActivityInfos進(jìn)行處理      
  pushDestroyedActivityInfo(activity);      
  synchronized (mDestroyedActivityInfos) {       
     mDestroyedActivityInfos.notifyAll();       
{  
 }};

3、檢測(cè)泄露實(shí)現(xiàn)

1.檢測(cè)線程

LeakCanay檢測(cè)實(shí)現(xiàn),舊版本是在一個(gè)HandlerThread 輪詢檢測(cè),現(xiàn)在發(fā)生改變,先在主線程中觸發(fā)檢測(cè),由RefWatcher.watch主動(dòng)觸發(fā),對(duì)activity,F(xiàn)ragment,F(xiàn)ragment.view的檢測(cè),即由生命周期觸發(fā),然后在

非主線程中進(jìn)行真正的check。

現(xiàn)在主線中被動(dòng)觸發(fā)檢測(cè)依據(jù)如下:

class RefWatcher{
   
 fun watch( watchedInstance: Any,name: String) {
    ...
    watchedInstances[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
   }
}


internal object InternalLeakSentry {

  ...
  private val checkRetainedExecutor = Executor {
    mainHandler.postDelayed(it, LeakSentry.config.watchDurationMillis)
  }
  val refWatcher = RefWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      onInstanceRetained = { listener.onReferenceRetained() },
      isEnabled = { LeakSentry.config.enabled }
  )
...
}

從moveToRetained調(diào)用,最終輾轉(zhuǎn)到HeapDumpTrigger的方法scheduleRetainedInstanceCheck方法,然后在非主線中進(jìn)行真正check,代碼如下:

internal class HeapDumpTrigger() {
 private fun scheduleRetainedInstanceCheck(reason: String) {
    if (checkScheduled) {
      CanaryLog.d("Already scheduled retained check, ignoring ($reason)")
      return
    }
    checkScheduled = true
    //非主線程hanlder
    backgroundHandler.post {
      checkScheduled = false
      checkRetainedInstances(reason)
    }
  }
...
}

ResourcePlugin參考LeakCanary舊版本,采用線程輪詢檢測(cè),依據(jù)如下:

//ActivityRefWatcher.start
private void scheduleDetectProcedure() {

    //檢測(cè)輪詢 mScanDestroyedActivitiesTask execute函數(shù)一直返回RetryableTask.Status.RETRY
  mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
}
class RetryableTaskExecutor{
     private void postToBackgroundWithDelay(final RetryableTask task, final int failedAttempts) {
         //非主線程 handler
         mBackgroundHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                RetryableTask.Status status = task.execute();
                if (status == RetryableTask.Status.RETRY) {
                    postToBackgroundWithDelay(task, failedAttempts + 1);
                }
            }
        }, mDelayMillis);
    }
}

** 2、檢測(cè)泄露邏輯實(shí)現(xiàn)**

** LeakCanay Check檢測(cè)**

原理:VM會(huì)將可回收的對(duì)象加入 WeakReference 關(guān)聯(lián)的 ReferenceQueue

1)根據(jù)retainedReferenceCount > 0,觸發(fā)一次gc請(qǐng)求,再次獲取retainedReferenceCount

var retainedReferenceCount = refWatcher.retainedInstanceCount

    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = refWatcher.retainedInstanceCount
    }

2)判斷retainedReferenceCount 是否大于retainedVisibleThreshold(默認(rèn)為5),小于則跳過接下來的檢測(cè)

if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

3)根據(jù)dumpHeapWhenDebugging開關(guān)和是否在Debug調(diào)試,如果配置開關(guān)開啟且在調(diào)試,則延時(shí)輪詢等待,調(diào)試結(jié)束

if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      showRetainedCountWithDebuggerAttached(retainedReferenceCount)
      scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
      return
    }

4)dump Hprof文件

 val heapDumpFile = heapDumper.dumpHeap()
 if (heapDumpFile == null) {
    showRetainedCountWithHeapDumpFailed(retainedReferenceCount)
    return}

5)開啟HeapAnalyzerService進(jìn)行Hprof分析

在舊版本中,在個(gè)別系統(tǒng)上可能存在誤報(bào),原因大致如下:

  • VM 并沒有提供強(qiáng)制觸發(fā) GC 的 API ,通過 System.gc()Runtime.getRuntime().gc()只能“建議”系統(tǒng)進(jìn)行 GC ,如果系統(tǒng)忽略了我們的 GC 請(qǐng)求,可回收的對(duì)象就不會(huì)被加入 ReferenceQueue

  • 將可回收對(duì)象加入 ReferenceQueue 需要等待一段時(shí)間,LeakCanary 采用延時(shí) 100ms 的做法加以規(guī)避,但似乎并不絕對(duì)管用

  • 監(jiān)測(cè)邏輯是異步的,如果判斷 Activity 是否可回收時(shí)某個(gè) Activity 正好還被某個(gè)方法的局部變量持有,就會(huì)引起誤判

  • 若反復(fù)進(jìn)入泄漏的 Activity ,LeakCanary 會(huì)重復(fù)提示該 Activity 已泄漏

現(xiàn)在這個(gè)2.0-alpha-2版本也沒有進(jìn)行排重,當(dāng)然這個(gè)也不好說,假如一個(gè)Activity有多處泄露,且泄露原因不同,排重 就會(huì)導(dǎo)致漏報(bào)。

ResourcePlugin Check檢測(cè)

原理:直接通過WeakReference.get()來判斷對(duì)象是否已被回收,避免因延遲導(dǎo)致誤判

1)判斷當(dāng)前mDestroyedActivityInfos是否空,為空的話,就沒必要泄露,因?yàn)槭禽喸?,所以要防止CPU空轉(zhuǎn),浪費(fèi)電

// If destroyed activity list is empty, just wait to save power.
while (mDestroyedActivityInfos.isEmpty()) {
    synchronized (mDestroyedActivityInfos) {
        try {
               mDestroyedActivityInfos.wait();
        } catch (Throwable ignored) {
           // Ignored.
        }
    }
}

2)根據(jù)配置開關(guān)和是否在Debug調(diào)試,如果配置開關(guān)開啟且在調(diào)試,跳過此次check,等待下次輪詢,調(diào)試結(jié)束

// Fake leaks will be generated when debugger is attached.
if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) {
        MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed.");
        return Status.RETRY;
}

3)增加一個(gè)一定能被回收的“哨兵”對(duì)象,用來確認(rèn)系統(tǒng)確實(shí)進(jìn)行了GC,沒有進(jìn)行GC,則跳過此次check,等待下次輪詢

final WeakReference<Object> sentinelRef = new WeakReference<>(new Object());
triggerGc();
if (sentinelRef.get() != null) {
   // System ignored our gc request, we will retry later.
   MatrixLog.d(TAG, "system ignore our gc request, wait for next detection.");
   return Status.RETRY;
}

4)對(duì)已判斷為泄漏的Activity,記錄其類名,避免重復(fù)提示該Activity已泄漏,有效期一天

final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
if (isPublished(destroyedActivityInfo.mActivityName)) {
    MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName);
    infoIt.remove();
    continue;
}

前面已經(jīng)提過排重還是有缺陷的,比如一個(gè)Activity有多處泄露,且泄露原因不同,排重 就會(huì)導(dǎo)致漏報(bào)

5)若發(fā)現(xiàn)某個(gè)Activity無法被回收,再重復(fù)判斷3次,且要求從該Activity被記錄起有2個(gè)以上的Activity被創(chuàng)建才認(rèn)為是泄漏,以防在判斷時(shí)該Activity被局部變量持有導(dǎo)致誤判

++destroyedActivityInfo.mDetectedCount;
long createdActivityCountFromDestroy = mCurrentCreatedActivityCount.get() - destroyedActivityInfo.mLastCreatedActivityCount;
if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
                    || (createdActivityCountFromDestroy < CREATED_ACTIVITY_COUNT_THRESHOLD && !mResourcePlugin.getConfig().getDetectDebugger())) {
    // Although the sentinel tell us the activity should have been recycled,
    // system may still ignore it, so try again until we reach max retry times.
   continue;
}

6.根據(jù)是否設(shè)置了mHeapDumper(即配置快關(guān)),若設(shè)置了,進(jìn)行dumpHeap,然后開啟服務(wù)CanaryWorkerService,進(jìn)行shrinkHprofAndReport,否則進(jìn)行簡(jiǎn)單的onDetectIssue

if (mHeapDumper != null) {
    final File hprofFile = mHeapDumper.dumpHeap();
    if (hprofFile != null) {
        markPublished(destroyedActivityInfo.mActivityName);
        final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
        mHeapDumpHandler.process(heapDump);
        infoIt.remove();
    } else {
         infoIt.remove();
     }
} else {
                   
       markPublished(destroyedActivityInfo.mActivityName);
       if (mResourcePlugin != null) {
             ...           
             mResourcePlugin.onDetectIssue(new Issue(resultJson));
                  
       }
}

4、Hprof裁剪和分析(暫時(shí)不詳細(xì)分析)

LeakCanary沒有對(duì)Hprof文件進(jìn)行shrink裁剪,使用haha進(jìn)行解析,分析出其泄露對(duì)象的GC Root引用鏈,把檢測(cè)和分析都放在客戶端。

ResourcePlugin只有檢測(cè)和Hprof文件shrink功能,不支持在客戶端Hprof文件,需要利用其提供的jar單獨(dú)對(duì)進(jìn)行分析Hprof,在分析過程中也可以把找出冗余Bitmap的GC ROOT鏈。

裁剪Hprof文件源碼見:HprofBufferShrinker().shrink

冗余Bitmap分析器:DuplicatedBitmapAnalyzer

Activity泄露分析器:ActivityLeakAnalyzer

Hprof 文件的大小一般約為 Dump 時(shí)的內(nèi)存占用大小,Dump 出來的 Hprof 大則一百多M,,如果不做任何處理直接將此 Hprof 文件上傳到服務(wù)端,一方面會(huì)消耗大量帶寬資源,另一方面服務(wù)端將 Hprof 文件長期存檔時(shí)也會(huì)占用服務(wù)器的存儲(chǔ)空間。

通過分析 Hprof 文件格式可知,Hprof 文件中 buffer 區(qū)存放了所有對(duì)象的數(shù)據(jù),包括字符串?dāng)?shù)據(jù)、所有的數(shù)組等,而我們的分析過程卻只需要用到部分字符串?dāng)?shù)據(jù)和 Bitmap 的 buffer 數(shù)組,其余的 buffer 數(shù)據(jù)都可以直接剔除,這樣處理之后的 Hprof 文件通常能比原始文件小 1/10 以上。

LeakCanary 中的引用鏈查找算法都是針對(duì)單個(gè)目標(biāo)設(shè)計(jì)的,ResourceCanary 中查找冗余 Bitmap 時(shí)可能找到多個(gè)結(jié)果,如果分別對(duì)每個(gè)結(jié)果中的 Bitmap 對(duì)象調(diào)用該算法,在訪問引用關(guān)系圖中的節(jié)點(diǎn)時(shí)會(huì)遇到非常多的重復(fù)訪問的節(jié)點(diǎn),降低了查找效率。為此我們修改了 LeakCanary 的引用鏈查找算法,使其在一次調(diào)用中能同時(shí)查找多個(gè)目標(biāo)到 GC Root 的最短引用鏈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容