Android | App內(nèi)存優(yōu)化 之 內(nèi)存泄漏 要點(diǎn)概述 以及 解決實(shí)戰(zhàn)

本文目錄:

  • 內(nèi)存泄漏的定義、表現(xiàn)、危害、情景,及避免OOM的技巧
  • Memory Analyzer Tool(MAT)簡(jiǎn)述、下載、安裝
  • 內(nèi)存泄漏解決實(shí)戰(zhàn)
  • 解決方法小結(jié)

內(nèi)存泄漏的定義、表現(xiàn)、危害、情景,及避免OOM的技巧

定義

  • Android內(nèi)存泄漏指的是進(jìn)程中某些對(duì)象(垃圾對(duì)象)已經(jīng)沒(méi)有使用價(jià)值了,
    但是它們卻可以直接間接地引用到gc roots導(dǎo)致無(wú)法被GC回收。

    無(wú)用的對(duì)象占據(jù)著內(nèi)存空間,使得實(shí)際可使用內(nèi)存變小,形象地說(shuō)法就是內(nèi)存泄漏了。


表現(xiàn)

  • 內(nèi)存抖動(dòng)、可用內(nèi)存逐漸變少

    上一篇博客寫(xiě)到,
    內(nèi)存抖動(dòng)可能是
    因?yàn)?code>代碼邏輯問(wèn)題 導(dǎo)致內(nèi)存被不斷地進(jìn)行分配回收
    當(dāng)然一個(gè)地方它的內(nèi)存一直在抖動(dòng),
    還有可能是由于內(nèi)存泄漏引起的,
    比如說(shuō),內(nèi)存泄漏 導(dǎo)致 可用內(nèi)存逐漸減少,
    這時(shí)候系統(tǒng)為了增加可用內(nèi)存,就會(huì)一直不斷地進(jìn)行GC
    導(dǎo)致內(nèi)存一直在抖動(dòng)??!


危害

內(nèi)存不足 -- GC頻繁 -- OOM

可能出現(xiàn)、需要注意的情景

  • .
    1. 非靜態(tài)內(nèi)部類(lèi)的靜態(tài)實(shí)例
    (“類(lèi)”是這個(gè)類(lèi)的類(lèi)型,實(shí)例是new 出來(lái)的實(shí)例)

    非靜態(tài)內(nèi)部類(lèi)會(huì)維持一個(gè)到外部類(lèi)實(shí)例的引用,
    如果非靜態(tài)內(nèi)部類(lèi)的實(shí)例是靜態(tài)的,
    就會(huì)間接長(zhǎng)期維持著外部類(lèi)的引用,阻止被回收掉。
    解決辦法
    使用靜態(tài)內(nèi)部類(lèi),
    靜態(tài)內(nèi)部類(lèi)實(shí)例,不會(huì)維持一個(gè)到外部類(lèi)實(shí)例的引用!


    2.多線(xiàn)程相關(guān)的匿名內(nèi)部類(lèi)和非靜態(tài)內(nèi)部類(lèi)
    匿名內(nèi)部類(lèi)同樣會(huì)持有外部類(lèi)的引用,
    如果在線(xiàn)程中執(zhí)行耗時(shí)操作
    就有可能發(fā)生內(nèi)存泄漏,導(dǎo)致外部類(lèi)無(wú)法被回收,直到耗時(shí)任務(wù)結(jié)束,
    解決辦法
    在頁(yè)面退出時(shí)結(jié)束線(xiàn)程中的任務(wù)


    3. Handler臨時(shí)性?xún)?nèi)存泄露
    Handler導(dǎo)致的內(nèi)存泄漏也可以被歸納為非靜態(tài)內(nèi)部類(lèi)實(shí)例(這里特指Handler實(shí)例)導(dǎo)致的;

    Handler通過(guò)發(fā)送Message與主線(xiàn)程交互,
    Message發(fā)出之后是存儲(chǔ)MessageQueue中的,
    有些Message也不是馬上被處理的。
    Message中存在一個(gè)target,是指向Handler的一個(gè)引用,
    如果MessageQueue存在的時(shí)間過(guò)長(zhǎng),
    就會(huì)導(dǎo)致Handler無(wú)法被回收

    如果Handler非靜態(tài)的,
    則會(huì)導(dǎo)致Handler外部類(lèi),如Activity或者Service不會(huì)被回收。

    由于AsyncTask內(nèi)部也是Handler機(jī)制,同樣存在內(nèi)存泄漏的風(fēng)險(xiǎn)。
    此種內(nèi)存泄露,一般是臨時(shí)性的。

    解決辦法
    A.使用靜態(tài)handler,外部類(lèi)引用使用弱引用處理
    B.在退出頁(yè)面時(shí)移除消息隊(duì)列中的消息


    4.Context導(dǎo)致內(nèi)存泄漏
    根據(jù)場(chǎng)景確定使用ActivityContext還是ApplicationContext
    因?yàn)槎?code>生命周期不同,
    對(duì)于不必須使用ActivityContext的場(chǎng)景(Dialog),一律采用ApplicationContext?。。?br> 單例模式最常見(jiàn)的發(fā)生此泄漏的場(chǎng)景,
    比如傳入一個(gè)ActivityContext靜態(tài)類(lèi)引用,導(dǎo)致無(wú)法回收

    5.靜態(tài)View導(dǎo)致泄漏
    使用靜態(tài)View可以避免每次啟動(dòng)Activity都去讀取渲染View?。?!
    但是靜態(tài)View會(huì)持有Activity引用,導(dǎo)致無(wú)法回收?。?!
    解決辦法
    Activity銷(xiāo)毀的時(shí)候?qū)?code>靜態(tài)View設(shè)置為null
    View一旦被加載到界面中將會(huì)持有一個(gè)Context對(duì)象引用
    在這里,這個(gè)context對(duì)象是我們的Activity,?。。?br> 聲明一個(gè)靜態(tài)變量 引用這個(gè)View,也就引用activity


    6.WebView導(dǎo)致的內(nèi)存泄漏
    WebView只要使用一次內(nèi)存就不會(huì)被釋放,
    所以WebView都存在內(nèi)存泄漏的問(wèn)題,!??!
    通常的解決辦法:
    WebView 單開(kāi)一個(gè)進(jìn)程
    使用AIDL進(jìn)行通信,根據(jù)業(yè)務(wù)需求在合適的時(shí)機(jī)釋放掉?。?/strong>

    7. 資源對(duì)象未關(guān)閉
    資源性對(duì)象Cursor、File、Socket等,
    內(nèi)部往往都使用了緩沖,容易造成內(nèi)存泄漏,
    應(yīng)該在使用后及時(shí)關(guān)閉。
    未在finally中關(guān)閉,
    會(huì)導(dǎo)致異常情況下資源對(duì)象未被釋放的隱患。

    8.集合中的對(duì)象未清理
    我們通常把一些對(duì)象的引用加入到了集合容器(比如ArrayList)中,
    當(dāng)我們不需要集合中的某個(gè)對(duì)象時(shí),
    如果沒(méi)有把它的引用從集合中清理掉,這個(gè)集合就會(huì)越來(lái)越大。
    如果這個(gè)集合是static的話(huà),那情況就更嚴(yán)重了。
    所以要在退出程序之前,
    需要調(diào)用集合實(shí)例的clear() 將集合里的東西clear掉,
    然后將集合實(shí)例置為null,再退出程序。


    9.Bitmap導(dǎo)致內(nèi)存泄漏
    bitmap是比較占內(nèi)存的,所以一定要在不使用的時(shí)候及時(shí)進(jìn)行清理;
    同時(shí)避免靜態(tài)變量持有大的bitmap對(duì)象;


    10.監(jiān)聽(tīng)器未關(guān)閉,注冊(cè)對(duì)象未反注冊(cè)
    很多需要registerunregister系統(tǒng)服務(wù)
    要在合適的時(shí)候進(jìn)行unregister,手動(dòng)添加的listener也需要及時(shí)移除


    11. 類(lèi)的靜態(tài)變量持有大數(shù)據(jù)對(duì)象
    靜態(tài)變量長(zhǎng)期維持到大數(shù)據(jù)對(duì)象的引用,阻止垃圾回收。

如何避免OOM?

1.Bitmap優(yōu)化
Bitmap非常消耗內(nèi)存,
而且在A(yíng)ndroid中,讀取bitmap時(shí),
一般分配給虛擬機(jī)的圖片堆棧只有8M,所以經(jīng)常造成OOM問(wèn)題。
所以有必要針對(duì)Bitmap的使用作出優(yōu)化:

1.1. 圖片顯示:加載合適尺寸的圖片,比如顯示縮略圖的地方不要加載大圖。

1.2. 圖片回收:使用完bitmap,及時(shí)使用Bitmap.recycle()回收。
問(wèn)題:Android不是自身具備垃圾回收機(jī)制嗎?此處為何要手動(dòng)回收。

Bitmap對(duì)象不是new生成的,而是通過(guò)BitmapFactory生產(chǎn)的。
通過(guò)源碼可發(fā)現(xiàn)是通過(guò)調(diào)用JNI生成Bitmap對(duì)象nativeDecodeStream()等方法)。
所以,
加載bitmap到內(nèi)存里包括兩部分,
Dalvik(ART)內(nèi)存Linux kernel內(nèi)存
前者會(huì)被虛擬機(jī)自動(dòng)回收。
后者必須通過(guò)recycle()方法,
內(nèi)部調(diào)用nativeRecycle()linux kernel回收。

1.3. 捕獲OOM異常:程序中設(shè)定如果發(fā)生OOM的應(yīng)急處理方式。

1.4. 圖片緩存:內(nèi)存緩存、硬盤(pán)緩存等

1.5. 圖片壓縮:直接使用ImageView顯示Bitmap時(shí)會(huì)占很多資源,
尤其當(dāng)圖片較大時(shí)容易發(fā)生OOM。
可以使用BitMapFactory.Options對(duì)圖片進(jìn)行壓縮。


1.6. 圖片像素(質(zhì)量):android默認(rèn)顏色模式為ARGB_8888, 顯示質(zhì)量最高,占用內(nèi)存最大。
若要求不高時(shí)可采用RGB_565等模式。
還可以使用WebP;
圖片大小圖片長(zhǎng)度 * 寬度 * 單位像素 所占據(jù)字節(jié)數(shù)

ARGB_4444:每個(gè)像素占用2byte內(nèi)存
ARGB_8888:每個(gè)像素占用4byte內(nèi)存 (默認(rèn))
RGB_565:每個(gè)像素占用2byte內(nèi)存

1.7. 考慮使用inBitmap圖片優(yōu)化之inBitmap

2. 巧用對(duì)象引用類(lèi)型

  • 強(qiáng)引用 strong:Object object=new Object()。
    當(dāng)內(nèi)存不足時(shí),Java虛擬機(jī)寧愿拋出OOM內(nèi)存溢出異常,
    也不會(huì)輕易回收強(qiáng)引用對(duì)象來(lái)解決內(nèi)存不足問(wèn)題;
    軟引用 soft:只有當(dāng)內(nèi)存達(dá)到某個(gè)閾值時(shí)才會(huì)去回收,常用于緩存;
    弱引用 weak :只要被GC線(xiàn)程掃描到了就進(jìn)行回收;
    虛引用

  • 如果想要避免OOM發(fā)生,則使用軟引用對(duì)象,即當(dāng)內(nèi)存快不足時(shí)進(jìn)行回收;
    如果想盡快回收某些占用內(nèi)存較大的對(duì)象,例如bitmap,可以使用弱引用,能被快速回收。
    不過(guò)如果要對(duì)bitmap作緩存就不要使用弱引用,因?yàn)楹芸炀蜁?huì)被GC回收,導(dǎo)致緩存失敗。

3. 使用 池 pool 內(nèi)存對(duì)象重復(fù)利用

  • 對(duì)象池:如果某個(gè)對(duì)象在創(chuàng)建時(shí),需要較大的資源開(kāi)銷(xiāo),
    那么可以將其放入對(duì)象池,
    即將對(duì)象保存起來(lái),下次需要時(shí)直接取出使用,
    而不用再次創(chuàng)建對(duì)象。
    當(dāng)然,維護(hù)對(duì)象池也需要一定開(kāi)銷(xiāo),故要衡量。

a.ListView/GridView源碼可以看到重用的情況ConvertView的復(fù)用。
RecyclerViewRecycler源碼。
b.Bitmap的復(fù)用
Listview等要顯示大量圖片。
需要使用LRU緩存機(jī)制來(lái)復(fù)用圖片。

  • 線(xiàn)程池:與對(duì)象池差不多,
    將線(xiàn)程對(duì)象放在池中供反復(fù)使用,減少反復(fù)創(chuàng)建線(xiàn)程的開(kāi)銷(xiāo)。

4. 使用更加輕量的數(shù)據(jù)結(jié)構(gòu):
考慮適當(dāng)?shù)那闆r下,
使用更加高效的安卓專(zhuān)門(mén)為手機(jī)研發(fā)的數(shù)據(jù)結(jié)構(gòu)類(lèi)
ArrayMap/SparseArray/SparseLongMap/SparseIntMap/SparseBoolMap
替代HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu)。
HashMap.put(string,Object);Object o = map.get(string);會(huì)導(dǎo)致一些沒(méi)必要的自動(dòng)裝箱拆箱。
HashMap,HashMap更耗內(nèi)存,
因?yàn)樗枰?code>額外的實(shí)例對(duì)象來(lái)記錄Mapping操作,
SparseArray更加高效,
因?yàn)樗苊饬?code>Key Value的自動(dòng)裝箱,和裝箱后解箱操作;

5. StringBuilder替代String: 在有些時(shí)候,
代碼中會(huì)需要使用到大量的字符串拼接的操作,
這種時(shí)候有必要考慮使用StringBuilder來(lái)替代頻繁的“+”

6.避免在類(lèi)似onDraw這樣的方法中創(chuàng)建對(duì)象,
因?yàn)樗鼤?huì)迅速占用大量?jī)?nèi)存,引起頻繁的GC甚至內(nèi)存抖動(dòng)

參考文章


Memory Analyzer Tool(MAT)簡(jiǎn)述、下載、安裝

  • 一個(gè)強(qiáng)大的Java Heap 工具,
    相對(duì)于Memory Profiler(MP)的簡(jiǎn)單分析,
    MAT可以對(duì)Java內(nèi)存做一個(gè)深入的分析;

  • MP不能確認(rèn)問(wèn)題,確認(rèn)問(wèn)題需要MAT

  • MAT下載鏈接
    這里可以下載獨(dú)立版MAT工具;

MAT安裝及使用教程
文件下載下來(lái)的是一個(gè)zip壓縮包,

解壓壓縮包,
進(jìn)入到如下目錄,
雙擊對(duì)應(yīng)的exe文件,
即可開(kāi)始使用:

工具初始界面如下:

  • 轉(zhuǎn)換:hprof-conv 原文件路徑 轉(zhuǎn)換后文件路徑


內(nèi)存泄漏解決實(shí)戰(zhàn)

  • 程序使用Bitmap來(lái)舉例,
    因?yàn)閷?duì)于一般程序,最大的問(wèn)題往往都在Bitmap上,
    因?yàn)樗牡膬?nèi)存非常多,
    將其作為內(nèi)存泄漏的案例,效果會(huì)比較明顯;

定義類(lèi)似觀(guān)察者的一個(gè)接口CallBack
以及類(lèi)似被觀(guān)察者的一個(gè)實(shí)現(xiàn)類(lèi)CallBackManager:

public interface CallBack {
    void dpOperate();
}

----

public class CallBackManager {

    public static ArrayList<CallBack> sCallBacks = new ArrayList<>();

    public static void addCallBack(CallBack callBack) {
        sCallBacks.add(callBack);
    }

    public static void removeCallBack(CallBack callBack) {
        sCallBacks.remove(callBack);
    }

}

activity_memoryleak.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_memoryleak"
        android:layout_width="50dp"
        android:layout_height="50dp" />

</LinearLayout>

MemoryLeakActivity.java:

/**
 * 模擬內(nèi)存泄露的Activity
 */
public class MemoryLeakActivity extends AppCompatActivity implements CallBack{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memoryleak);
        ImageView imageView = findViewById(R.id.iv_memoryleak);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.splash);
        imageView.setImageBitmap(bitmap);

        CallBackManager.addCallBack(this);
    }

    @Override
    public void dpOperate() {
        // do sth
    }
}
  • dpOperate()模擬操作業(yè)務(wù)的邏輯方法;
    在反復(fù)地退出關(guān)閉和打開(kāi)進(jìn)入 以上界面的時(shí)候,
    就可能引起內(nèi)存泄漏;

  • 運(yùn)行程序,打開(kāi)MP,初始曲線(xiàn)圖是平穩(wěn)的:

  • 建立一個(gè)簡(jiǎn)單的界面,如MainActivity
    可以點(diǎn)擊進(jìn)入MemoryLeakActivity,
    然后不斷地在MainActivity和MemoryLeakActivity之間切換,
    即反復(fù)地退出關(guān)閉和打開(kāi)進(jìn)入 MemoryLeakActivity,
    這時(shí)候可以看到MP的圖示,內(nèi)存曲線(xiàn)在階梯狀上升,
    也就是說(shuō)我們的可用內(nèi)存在逐漸減少了,?。?!
    出現(xiàn)了這種情況,我們基本就可以斷定,界面可能就是出現(xiàn)了內(nèi)存泄漏?。。。?/strong>
  • MP工具這里,
    只能幫我們大致斷定這個(gè)界面是出現(xiàn)了內(nèi)存泄漏,
    但是它沒(méi)有辦法幫助我們 斷定那個(gè)地方有 內(nèi)存泄漏,
    來(lái)讓我們有的放矢 修改代碼;
    這里就需要MAT上場(chǎng)了;

  • 首先需要點(diǎn)擊堆轉(zhuǎn)儲(chǔ)按鈕,
    MP工具會(huì)記錄一段時(shí)間的內(nèi)存分配情況,
    然后我們可以對(duì)這段記錄進(jìn)行Dump,
    下載成文件保存在本地:

  • 保存成功:

  • 接著,
    在A(yíng)S的下方的Terminal終端欄,

Android studio 進(jìn)入 adb 命令 ---使用terminal 終端 進(jìn)入sdk 找到 platform-tools 目錄進(jìn)入即可

  • 使用cd指令,
    進(jìn)入到配套SDK目錄下的platform-tools目錄,
    回車(chē),到達(dá)工具目錄;

  • 接著在使用platform-tools目錄目錄下,
    使用hprof-conv工具指令,
    轉(zhuǎn)化堆轉(zhuǎn)儲(chǔ)保存下來(lái)的文件:

  • 回車(chē)后,轉(zhuǎn)換成功:

  • 接著如文首下載好獨(dú)立版本的MAT工具,
    使用MAT工具,打開(kāi)我們剛剛轉(zhuǎn)化完的文件
    (工具界面左上角File ---> openFile ---> 選擇文件打開(kāi)):

  • 打開(kāi)之后,MAT 就會(huì)對(duì)我們的 堆轉(zhuǎn)儲(chǔ)轉(zhuǎn)換后的文件 進(jìn)行分析:

  • 接下來(lái)目的是通過(guò)MAT來(lái)找到內(nèi)存泄漏的位置,
    點(diǎn)擊左下角有個(gè)Histogram:

  • 進(jìn)入Histogram界面后,可以看到最上面有個(gè)匹配搜索:

  • 這里搜索我們Activity的名字:MemoryLeak

    可以看到搜索結(jié)果,
    可以看到這里Objects這里顯示著有18個(gè),
    也就是說(shuō)現(xiàn)在內(nèi)存當(dāng)中,竟存在著18個(gè)MemoryLeakActivity實(shí)例
    (emmmmm,也就是我們剛剛在試驗(yàn)的時(shí)候,
    反復(fù)退出進(jìn)入了18次MemoryLeakActivity),
    這是非常不合理的!

    后面是
    Shallow Heap:堆中 此類(lèi)型所有實(shí)例 的總大?。ㄒ宰止?jié)為單位)
    Retained Heap:為此 類(lèi)型的所有實(shí)例 而 保留的內(nèi)存總大小(以字節(jié)為單位)

    接下來(lái),點(diǎn)擊搜索出來(lái)的實(shí)例,右鍵,
    選擇List objects -> with incoming references,
    (with incoming reference
    incoming 指過(guò)來(lái)
    即指的是 引用到選中實(shí)例實(shí)例,即查看本實(shí)例被誰(shuí)引用;

    with outcoming references
    outcoming 指出去
    該選中實(shí)例引用的實(shí)例,即查看本實(shí)例引用了誰(shuí);)
    下面是點(diǎn)擊后彈出的搜索結(jié)果:

    選擇第一個(gè)結(jié)果Item,右鍵,
    Merge Shortest Path To GC Roots ---> exclude all phantom/weak/soft etc. references:
    查看這個(gè)對(duì)象GC Root 之間的一個(gè)路徑---exclude(除了)虛、弱引用、軟引用之外,即只看強(qiáng)引用。
    (從GC上說(shuō),除了強(qiáng)引用外,
    其他的引用在JVM需要的情況下是都可以 被GC掉的?。?!
    所以?。。?br> 如果一個(gè)對(duì)象始終無(wú)法被GC,就是因?yàn)閺?qiáng)引用的存在,?。?!
    從而導(dǎo)致在GC的過(guò)程中一直得不到回收,
    因此就內(nèi)存溢出了)
    下面是分析結(jié)果:
    我們可以看到,
    它從左上角往下是一個(gè)GC路徑,
    可以看到最后一個(gè)行的item其圖標(biāo)左下角有一個(gè)小圓圈,
    這是我們真正要關(guān)注的地方,
    如上圖所示,也就是說(shuō)最終MAT這里給了我們的一個(gè)信息,
    即上述所說(shuō)的17個(gè) MemoryLeakActivity 實(shí)例實(shí)際上
    是被CallBackManager這個(gè)類(lèi)中的sCallBack這個(gè)實(shí)例(對(duì)象)引用了,
    至此,我們便可以回到代碼中,尋找這個(gè)CallBackManager,去修改對(duì)應(yīng)的代碼;

  • 可以看到果然我們一開(kāi)始便定義了一個(gè)sCallBacks實(shí)例,
    MemoryLeakActivity 實(shí)例就一直被它所引用著?。。?!
    因?yàn)檫@里sCallBacks實(shí)例它被static修飾著,?。?!
    Android中被static修飾著的變量,它的生命周期是跟APP的整個(gè)周期 一樣長(zhǎng)的,

    所以我們打開(kāi)進(jìn)入MemoryLeakActivity的時(shí)候,
    onCreate()中我們就把當(dāng)前的一個(gè)MemoryLeakActivity實(shí)例加到sCallBacks這個(gè)List實(shí)例中去,
    而每次退出銷(xiāo)毀MemoryLeakActivity的時(shí)候,
    卻沒(méi)有在sCallBacks中移除剛剛添加的這個(gè)MemoryLeakActivity實(shí)例,
    而且MemoryLeakActivity被銷(xiāo)毀的時(shí)候,我們沒(méi)有退出APP,
    所以sCallBacks實(shí)例也一直存在,
    如此一來(lái),
    每次反復(fù)進(jìn)入打開(kāi)退出銷(xiāo)毀 MemoryLeakActivity的,
    sCallBacks實(shí)例就會(huì)多持有一個(gè)MemoryLeakActivity實(shí)例,
    多次進(jìn)出MemoryLeakActivity累積下來(lái),
    sCallBacks實(shí)例持有的引用就只增不減,
    而且這里的持有都是強(qiáng)應(yīng)用持有,不會(huì)被GC回收的,
    無(wú)用的對(duì)象占據(jù)的內(nèi)存空間就越來(lái)越大,實(shí)際可使用內(nèi)存逐漸變小,導(dǎo)致內(nèi)存泄漏了!!


    解決方法的話(huà),
    就是在onDestroy()中,
    MemoryLeakActivity自身從sCallBacks實(shí)例中移除了,
    這樣每次退出銷(xiāo)毀MemoryLeakActivity的時(shí)候,
    onCreate()中添加進(jìn)sCallBacks實(shí)例的MemoryLeakActivity自身,就會(huì)在銷(xiāo)毀前被移除,
    由此解決內(nèi)存泄漏:
/**
 * 模擬內(nèi)存泄露的Activity
 */
public class MemoryLeakActivity extends AppCompatActivity implements CallBack{

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memoryleak);
        ImageView imageView = findViewById(R.id.iv_memoryleak);
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.splash);
        imageView.setImageBitmap(bitmap);

        CallBackManager.addCallBack(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        CallBackManager.removeCallBack(this);
    }

    @Override
    public void dpOperate() {
        // do sth
    }
}

解決方法小結(jié)

  • 使用MP初步觀(guān)察,
    發(fā)現(xiàn)不斷上升或者居高不下內(nèi)存曲線(xiàn)
    可用內(nèi)存逐漸減少現(xiàn)象,
    便可以判斷這個(gè)地方是可能出現(xiàn)了內(nèi)存泄漏;

  • 使用MP堆轉(zhuǎn)儲(chǔ),
    將一段時(shí)間內(nèi)的分配情況記錄成文件,
    導(dǎo)出并保存這份文件,
    基于A(yíng)S的Terminal終端欄,
    使用hprof-conv工具指令
    轉(zhuǎn)化堆轉(zhuǎn)儲(chǔ)保存下來(lái)的文件;

  • 使用MAT打開(kāi)(OpenFile)并分析hprof-conv的轉(zhuǎn)化生成的文件;

  • 點(diǎn)擊進(jìn)入Histogram界面,
    篩選可疑實(shí)例;
    觀(guān)察Objects欄值是否異常;

  • 點(diǎn)擊對(duì)應(yīng)的實(shí)例,右鍵,
    選擇List objects -> with incoming references
    查看本實(shí)例被誰(shuí)引用;
    彈出的搜索結(jié)果界面;

    在上述彈出的搜索結(jié)果界面中,
    選擇一個(gè)結(jié)果Item,右鍵,
    Merge Shortest Path To GC Roots ---> exclude all phantom/weak/soft etc. references:
    查看這個(gè)對(duì)象跟 GC Root 之間的一個(gè)路徑---exclude(除了)虛、弱引用、軟引用之外,即只看強(qiáng)引用。
    彈出分析結(jié)果界面,界面中 根據(jù) 圖示(橙紅小圓圈) ,
    可以找到 引用選中實(shí)例實(shí)例對(duì)象 及其 所在的類(lèi)文件名;

  • 根據(jù)上述定位的 類(lèi)文件名 以及 持有引用對(duì)象名,
    找到相應(yīng)的位置
    排查并修改代碼,
    解決問(wèn)題;






參考自
最后編輯于
?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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