[性能優(yōu)化]使用LeakCanary優(yōu)化你的app

前言

在日常開發(fā)中,可能經(jīng)常會遇到一些莫名奇妙的崩潰問題,但是仔細查看代碼邏輯卻似乎也找不出代碼中有哪些不對的邏輯。這時候就需要仔細分析,你的代碼中是否存在內(nèi)存泄漏的問題。LeakCanary是Square公司開源的一款性能優(yōu)化工具,它能夠幫你方便的分析你的app中是否存在內(nèi)存泄漏的問題。在使用LeakCanary之前,讓我們先來了解幾個概念。

一些概念

  • Java虛擬機
    相信學(xué)過Android的你對Java虛擬機一定不會陌生,但還是簡單的介紹一下Java虛擬機。如果你學(xué)過C/C++,一些C/C++書中都會強調(diào)你需要手動分配內(nèi)存,并在使用之后手動回收內(nèi)存。但是好像Java中從來沒有人讓你釋放內(nèi)存啊,這一切都需要歸功于Java虛擬機,Java虛擬機能夠幫我們自動釋放可回收內(nèi)存。當然,java虛擬機除了能夠幫助我們自動回收內(nèi)存之外,還有很多別的功能,但本文的重點在于內(nèi)存泄漏,而安卓使用的虛擬機與Java虛擬機相似。所以下面我們聊一聊Java虛擬機的垃圾回收吧。

  • GC
    GC全稱(Garbage Collection),也就是垃圾回收,Java虛擬機主要通過兩種途徑自動幫我們回收內(nèi)存。

    • 引用計數(shù)
      Java虛擬機會給對象增加一個引用計數(shù)器,每當程序引用一次對象,計數(shù)器就會加一;反之,每當一個引用計數(shù)器失效時,計數(shù)器就會減一。當計數(shù)器的值為0,則說明此對象沒有被引用,可以被回收。
      舉個例子
      Object obj = new Object(); // 計數(shù)器 + 1 = 1
      obj = null; // 計數(shù)器 - 1 = 0,GC回收
      // 但是如果對象相互調(diào)用,引用計數(shù)器就無法使得GC回收
      Object a = new Object(); // a的引用計數(shù)為1 
      Object b = new Object(); // b的引用計數(shù)為1 
      a.next = b; // a的引用計數(shù)為2 
      b.next = a; // b的引用計數(shù)為2 
      a = null; // a的引用計數(shù)為1,盡管已經(jīng)顯示地將a賦值為null,但是由于引用計數(shù)為1,GC無法回收a
      b = null; // b的引用計數(shù)為1,同理,GC也不回收b
      

    為了解決對象之間相互引用導(dǎo)致的無法GC的問題,Java虛擬機還有另一種GC策略。

    • 可達性分析
      設(shè)立若干根對象(GC Root) ,每個對象都是一個子節(jié)點,當一個對象找不到根節(jié)點,也就是無人引用時,標志其不可達。
      可以作為GC Root的對象包括:
      1.jvm棧中引用的對象
      2.方法區(qū)中靜態(tài)變量引用的對象
      3.方法區(qū)中常量引用的對象
      4.本地方法棧中引用的對象
      5.新生代,活不了多久就死的對象,比如局部變量,用復(fù)制算法[1]
      6.老年代,生命周期長的對象,活的久不過也是會死的,用標記清除算法[2]
      7.永久代[3] -----基本上GC不回收

    但即使Java虛擬機已經(jīng)如此優(yōu)秀,它也不能保證所有的可回收內(nèi)存都能正常回收,

  • 內(nèi)存泄漏
    內(nèi)存泄漏是指在app運行的過程中,由于內(nèi)存并沒有合理的回收,如:生命周期長的對象持有了生命周期短的對象的引用,導(dǎo)致生命周期短的對象一直無法回收。當這種情況累積到一定程度,可分配的棧內(nèi)存不足的時候,就會導(dǎo)致OOM,我們就看到了程序崩潰。而且這種崩潰不像一般的程序崩潰那樣能夠復(fù)現(xiàn),所以直接由程序崩潰,導(dǎo)致了程序員崩潰。
    例如

    • 在onDestroy()調(diào)用Android活動實例的方法后,不再需要該活動實例,并且在靜態(tài)字段中存儲對該活動的引用將防止其被垃圾回收。
    • 添加一個Fragment到backstack而沒有在Fragment.onDestroyView()中清除它的view的成員。
    • 在一個對象中以成員的方式保存了一個Activity的context,而Activity在配置更改時依然存在。
    • 注冊的綁定生命周期對象的監(jiān)聽、廣播接收器、RxJava訂閱等,在生命周期結(jié)束的時候忘記取消注冊。
  • OOM
    OOM是(Out Of Memory)的簡稱,就是內(nèi)存不足的意思,類似的問題也有StackOverflow,寫一個簡單的沒有出口的遞歸函數(shù)就能看到。

使用LeakCanary

內(nèi)存泄漏在安卓app中十分常見,小內(nèi)存泄漏的積累會導(dǎo)致應(yīng)用內(nèi)存不足,并導(dǎo)致OOM崩潰。LeakCanary將幫助我們在開發(fā)期間找到這些內(nèi)存泄漏。
使用LeakCanary十分簡單,只需要找到·build.gradle·,并在·dependencies·中加入引用即可。

dependencies {
  // debugImplementation because LeakCanary should only run in debug builds.
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.1'
}

我們可以通過過濾LeakCanary標簽在Logcat中查看。

分析內(nèi)存泄漏

當發(fā)生內(nèi)存泄漏的時候,LeakCanary會自動幫你保存內(nèi)存泄漏信息,并將內(nèi)存泄漏相關(guān)的代碼以另一個app的形式展示給你,你可以根據(jù)提示,修改代碼,從而解決內(nèi)存泄漏的問題。

如何解決內(nèi)存泄漏呢

內(nèi)存泄漏常常存在的原因是因為兩個或多個對象生命周期不同,同時存在相互引用。導(dǎo)致生命周期短的對象被生命周期長的對象引用后無法正?;厥?,從而造成內(nèi)存泄漏。
下面是一個可能經(jīng)常遇見的內(nèi)存泄漏的小例子:

  • Activity內(nèi)存泄漏
    安卓開發(fā)中,我們時常會把Activity當做Context傳入某些單例如UserInfo等類中,而我們知道,單例的生命周期可能是整個Application的生命周期,遠遠要比Activity的生命周期要長。如果使用在單例中某些成員變量保存了Activity的引用,當Activity被關(guān)閉的時候,就會導(dǎo)致內(nèi)存泄漏了。所以,當我們寫代碼的時候,要格外的慎重,如果Context不是必須傳入Activity,我們可以將Context傳入Application的Context。如果實在非要傳入Activity,你可以在使用完Activity只有,將相關(guān)的成員變量置空,這個時候,如果發(fā)成GC,Activity的引用計數(shù)為0,自然就能正常GC了。

這應(yīng)該是年前寫的最后一篇了,希望這篇文章能夠幫到你。


  1. 最初是將內(nèi)存分為相等的兩塊,只用其中的一塊,當這塊內(nèi)存滿的時候,將其中存活的對象復(fù)制到另一塊內(nèi)存中,然后GC 回收釋放之前這塊內(nèi)存。 ?

  2. 就是遍歷GC Root,標記可達不可達對象,然后回收不可達對象,這種算法缺點是效率低,無法回收連續(xù)物理內(nèi)存,后來升級為標記 - 整理算法,將可達對象移動到內(nèi)存的一端,然后GC回收剩下部分連續(xù)的物理內(nèi)存。 ?

  3. 分代算法,在Java中,將內(nèi)存中的對象按照生命周期長短分成新生代,老年代,永久代 ?

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

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