Android內存泄漏問題

一、垃圾回收

一般來說刀疙,程序使用內存遵循先向操作系統(tǒng)申請一塊內存道宅,使用內存臼朗,使用完畢之后釋放內存歸還給操作系統(tǒng)。然而在傳統(tǒng)的C/C++等要求顯式釋放內存的編程語言中昏名,在合適的時候釋放內存是一個很有難度的工作涮雷,因此Java等編程語言都提供了基于垃圾回收算法的內存管理機制。

常見的垃圾回收算法有引用計數算法(Reference Counting)轻局、標注—清除算法(Mark and Sweep GC)洪鸭、復制算法(Copying GC)和逐代回收(Generational GC)等算法,其中Android虛擬機采用的是標注—清除算法仑扑,并不是大多數JVM實現(xiàn)里采用的逐代回收算法览爵。

二、幾個基本概念

  • 內存泄露:程序在向系統(tǒng)申請分配內存空間后(new)镇饮,在使用完畢后未釋放蜓竹,結果導致不再使用的對象一直占據該內存單元,或者他們占用的內存沒有及時得到釋放储藐,從而造成內存空間不斷減少的現(xiàn)象俱济。由于Android程序可以使用的內存較少,發(fā)生內存泄漏會導致內存更加緊張邑茄,甚至最終由于內存耗盡而發(fā)生OOM(out of memeroy)錯誤姨蝴,導致應用崩潰。

  • 內存溢出:程序向系統(tǒng)申請的內存空間超出了系統(tǒng)能給的肺缕。比如內存只能分配一個int類型左医,我卻要塞給他一個long類型,系統(tǒng)就出現(xiàn)oom同木。

  • 強引用:強引用是使用最普遍的引用浮梢。如果一個對象具有強引用,那垃圾回收器絕不會回收它彤路。 當內存空間不足秕硝,Java虛擬機寧愿拋出OOM(OutOfMemoryError)錯誤,使程序異常終止洲尊,也不會靠隨意回收具有強引用的對象來解決內存不足的問題远豺。

  • 軟引用:如果一個對象只具有軟引用,但內存空間足夠時坞嘀,垃圾回收器就不會回收它躯护;直到虛擬機報告內存不夠時才會回收, 只要垃圾回收器沒有回收它丽涩,該對象就可以被程序使用棺滞。軟引用可用來實現(xiàn)內存敏感的高速緩存裁蚁。 軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收继准,Java虛擬機就會把這個軟引用加入到與之關聯(lián)的引用隊列中枉证。

  • 弱引用:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區(qū)域的過程中移必,一旦發(fā)現(xiàn)了只具有弱引用的對象室谚,不管當前內存空間是否足夠,都會回收它的內存崔泵。 不過舞萄,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象管削。 弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用倒脓,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯(lián)的引用隊列中含思。

  • 虛引用:虛引用可以理解為虛設的引用崎弃,與其他幾種引用都不同,虛引用并不會決定對象的生命周期含潘。如果一個對象僅持有虛引用饲做,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收遏弱。 虛引用主要用來跟蹤對象被垃圾回收器回收的活動盆均。 虛引用與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。 當垃圾回收器準備回收一個對象時漱逸,如果發(fā)現(xiàn)它還有虛引用泪姨,就會在回收對象的內存之前,把這個虛引用加入到與之關聯(lián)的引用隊列中饰抒。 程序可以通過判斷引用隊列中是否已經加入了虛引用肮砾,來了解被引用的對象是否將要被垃圾回收。 如果程序發(fā)現(xiàn)某個虛引用已經被加入到引用隊列袋坑,那么就可以在所引用的對象的內存被回收之前采取必要的行動仗处。

三、Android內存泄漏存在的原因

1.靜態(tài)變量導致的內存泄漏
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    
    private static View mView;
    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);
        mView = new View (this);
    }
}

上面這段代碼就會產生內存泄漏枣宫,mView是靜態(tài)變量婆誓,它的內部持有當前的activity,因此Activity仍然無法釋放也颤。

2.單例模式導致的內存泄漏

不合理的單例模式也會引起內存泄漏洋幻,


public class LeakTest {

    private static LeakTest mInstance;

    private LeakTest(Context context) {
    }

    /**
     * 單例模式
     */
    public static LeakTest getInstance(Context context) {
        if(mInstance == null) {
            mInstance = new LeakTest (context);
        }
        return mInstance;
    }

}

比如以上代碼是一個提供單例的測試類,需要傳入一個Contex進行獲取歇拆,在Activity中使用的時候如果傳入了改Activity的Context鞋屈,當Activity不再使用的時候,如果在onDestroy時不進行操作故觅,就會造成靜態(tài)的單例變量一直引用這個Context厂庇,導致本該釋放內存的變量一直占用內存造成內存泄漏。要解決這種類型的內存泄漏可以在引用Activity的Context時變成引用Application的Context输吏。

3.屬性動畫導致內存泄漏

屬性動畫中有一類無限循環(huán)的動畫权旷,如果在Activity中播放此類動畫但是沒有在onDestroy中去停止動畫,那么動畫就會一直播放贯溅,盡管在當前界面以及看不到動畫了拄氯。這個時候Activity的View會被動畫持有,View又持有這個Activity它浅,最終導致Activity無法釋放译柏,造成內存泄漏。解決方法是在onDestroy的時候調用animator.cancle()方法來停止動畫姐霍。

4.其他原因導致的內存泄漏
  • 資源對象沒關閉造成的內存泄漏鄙麦,如查詢數據庫后沒有關閉游標cursor
  • 構造Adapter時,沒有使用 convertView 重用
  • Bitmap對象不再使用時镊折,沒有調用recycle()釋放內存
  • 對象被生命周期長的對象引用胯府,如activity被靜態(tài)變量引用導致activity不能釋放,因為靜態(tài)類生命周期比Activity長恨胚。
  • 想暫時規(guī)避內存泄漏問題,可以在manifest.xml加入:
android:largeHeap="true"

此時heapsize會增大2-3倍骂因,緩解OOM的發(fā)生
其他內存泄漏的原因參考:https://blog.csdn.net/cyq1028/article/details/19980369

四、內存泄漏的排查

1.LeakCanary

A memory leak detection library for Android and Java.

LeakCanary的工作機制:

  1. RefWatcher.watch() 創(chuàng)建一個 KeyedWeakReference 到要被監(jiān)控的對象赃泡。

  2. 然后在后臺線程檢查引用是否被清除寒波,如果沒有,調用GC升熊。

  3. 如果引用還是未被清除影所,把 heap 內存 dump 到 APP 對應的文件系統(tǒng)中的一個 .hprof 文件中。

  4. 在另外一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用HAHA 解析這個文件僚碎。

  5. 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference猴娩,定位內存泄露。

  6. HeapAnalyzer 計算 到 GC roots 的最短強引用路徑勺阐,并確定是否是泄露卷中。如果是的話,建立導致泄露的引用鏈渊抽。

  7. 引用鏈傳遞到 APP 進程中的 DisplayLeakService蟆豫, 并以通知的形式展示出來。

LeakCanary的使用

1.添加依賴

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
  // Optional, if you use support library fragments:
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}

2.新建一個Application類懒闷,用于使用LeakCanary

public class ExampleApplication extends Application {

 public static RefWatcher getRefWatcher(Context context) {
    ExampleApplication application = (ExampleApplication) context.getApplicationContext();
    return application.refWatcher;
  }

  private RefWatcher refWatcher;

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

LeakCanary.install() 會返回一個預定義的 RefWatcher十减,同時也會啟用一個 ActivityRefWatcher栈幸,用于自動監(jiān)控調用 Activity.onDestroy() 之后泄露的 activity,LeakCanary自動監(jiān)控Activity,如果要在Fragment中使用LeakCanary需要在onDestroy方法中進行監(jiān)控帮辟。

public abstract class BaseFragment extends Fragment {

  @Override public void onDestroy() {
    super.onDestroy();
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
    refWatcher.watch(this);
  }
}

只要繼承自基類的Fragment都會被監(jiān)控內存泄漏的情況速址。

3.LeakCanary的自定義和其他操作參考
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

內存泄漏

泄漏詳情及原因

2. Android Profiler

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市由驹,隨后出現(xiàn)的幾起案子芍锚,更是在濱河造成了極大的恐慌,老刑警劉巖蔓榄,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件并炮,死亡現(xiàn)場離奇詭異,居然都是意外死亡甥郑,警方通過查閱死者的電腦和手機逃魄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來澜搅,“玉大人嗅钻,你說我怎么就攤上這事〉暾梗” “怎么了养篓?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赂蕴。 經常有香客問我柳弄,道長,這世上最難降的妖魔是什么概说? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任碧注,我火速辦了婚禮,結果婚禮上糖赔,老公的妹妹穿的比我還像新娘萍丐。我一直安慰自己,他們只是感情好放典,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布逝变。 她就那樣靜靜地躺著,像睡著了一般奋构。 火紅的嫁衣襯著肌膚如雪壳影。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天弥臼,我揣著相機與錄音宴咧,去河邊找鬼。 笑死径缅,一個胖子當著我的面吹牛掺栅,可吹牛的內容都是我干的烙肺。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼氧卧,長吁一口氣:“原來是場噩夢啊……” “哼桃笙!你這毒婦竟也來了?” 一聲冷哼從身側響起假抄,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丽猬,沒想到半個月后宿饱,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡脚祟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年谬以,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片由桌。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡为黎,死狀恐怖,靈堂內的尸體忽然破棺而出行您,到底是詐尸還是另有隱情铭乾,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布娃循,位于F島的核電站炕檩,受9級特大地震影響,放射性物質發(fā)生泄漏捌斧。R本人自食惡果不足惜笛质,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捞蚂。 院中可真熱鬧妇押,春花似錦、人聲如沸姓迅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丁存。三九已至色冀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柱嫌,已是汗流浹背锋恬。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留编丘,地道東北人与学。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓彤悔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親索守。 傳聞我的和親對象是個殘疾皇子晕窑,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內容