三眶痰、Android性能優(yōu)化之常見的內存泄漏分析

內存泄漏分析:
往往做項目的時候情況非常復雜屁商,或者項目做得差不多了想起來要性能優(yōu)化檢查下內存泄露绢彤。

如何找到項目中存在的內存泄露的這些地方呢咸包?

1.確定是否存在內存泄露
1)Android Monitors的內存分析
最直觀的看內存增長情況,知道該動作是否發(fā)生內存泄露杖虾。
動作發(fā)生之前:GC完后內存1.4M; 動作發(fā)生之后:GC完后內存1.6M

2)使用MAT內存分析工具
MAT分析heap的總內存占用大小來初步判斷是否存在泄露
Heap視圖中有一個Type叫做data object烂瘫,即數據對象,也就是我們的程序中大量存在的類類型的對象。
在data object一行中有一列是“Total Size”坟比,其值就是當前進程中所有Java數據對象的內存總量芦鳍,
一般情況下,這個值的大小決定了是否會有內存泄漏葛账。
我們反復執(zhí)行某一個操作并同時執(zhí)行GC排除可以回收掉的內存柠衅,注意觀察data object的Total Size值,
正常情況下Total Size值都會穩(wěn)定在一個有限的范圍內籍琳,也就是說由于程序中的的代碼良好菲宴,沒有造成對象不被垃圾回收的情況。
反之如果代碼中存在沒有釋放對象引用的情況趋急,隨著操作次數的增多Total Size的值會越來越大喝峦。
那么這里就已經初步判斷這個操作導致了內存泄露的情況。

2.先找懷疑對象(哪些對象屬于泄露的)
MAT對比操作前后的hprof來定位內存泄露是泄露了什么數據對象呜达。(這樣做可以排除一些對象谣蠢,不用后面去查看所有被引用的對象是否是嫌疑)
快速定位到操作前后所持有的對象哪些是增加了(GC后還是比之前多出來的對象就可能是泄露對象嫌疑犯)
技巧:Histogram中還可以對對象進行Group,比如選擇Group By Package更方便查看自己Package中的對象信息查近。

  1. MAT分析hprof來定位內存泄露的原因所在眉踱。(哪個對象持有了上面懷疑出來的發(fā)生泄露的對象)
    1)Dump出內存泄露“當時”的內存鏡像hprof,分析懷疑泄露的類霜威;
    2)把上面2得出的這些嫌疑犯一個一個排查個遍谈喳。步驟:
    (1)進入Histogram,過濾出某一個嫌疑對象類
    (2)然后分析持有此類對象引用的外部對象(在該類上面點擊右鍵List Objects--->with incoming references)
    (3)再過濾掉一些弱引用戈泼、軟引用婿禽、虛引用,因為它們遲早可以被GC干掉不屬于內存泄露
    (在類上面點擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
    (4)逐個分析每個對象的GC路徑是否正常
    此時就要進入代碼分析此時這個對象的引用持有是否合理矮冬,這就要考經驗和體力了谈宛!
    (比如上課的例子中:旋轉屏幕后MainActivity有兩個,肯定MainActivity發(fā)生泄露了胎署,
    那誰導致他泄露的呢吆录?原來是我們的CommonUtils類持有了旋轉之前的那個MainActivity他,
    那是否合理琼牧?結合邏輯判斷當然不合理恢筝,由此找到內存泄露根源是CommonUtils類持有了該MainActivity實例造成的。
    怎么解決巨坊?罪魁禍首找到了撬槽,怎么解決應該不難了,不同情況解決辦法不一樣趾撵,要靠你的智慧了侄柔。)

判斷一個應用里面內存泄露避免得很好共啃,怎么看?
當app退出的時候暂题,這個進程里面所有的對象應該就都被回收了移剪,尤其是很容易被泄露的(View,Activity)是否還內存當中薪者。
可以讓app退出以后纵苛,查看系統(tǒng)該進程里面的所有的View、Activity對象是否為0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的數量是否為0.

System Information
System Information 選項
Memory Usage

發(fā)現任然有兩個activity沒有關閉

==================內存泄漏經常出現的例子=====================
內存泄漏(Memory Leak):
進程中某些對象已經沒有使用價值言津,但是他們卻還可以直接或間接地被引用到GC Root攻人,導致無法回收。
當內存泄漏過多的時候悬槽,再加上應用本身占用的內存怀吻,(日積月累)最終就會導致內存溢出OOM。
現象:當我們使用微信很長時間陷谱,也出現了程序崩潰現象烙博。(這就是內存泄漏日積月累導致OOM)
內存溢出(OOM):
當應用占用的heap資源超過了Dalvik虛擬機分配的內存就會內存溢出瑟蜈。比如:加載大量圖片烟逊。

1.靜態(tài)變量引起的內存泄漏
      當調用getInstance時,如果傳入的context時Activity的context铺根。只要這個單例沒有被釋放宪躯,那么Activity也不會被釋放一直到進程退出才會釋放。
public class CommonUtils {

    private static CommonUtils instance;

    private Context context;

    private CommonUtils(Context context) {
        this.context = context;
    }

    public static CommonUtils getInstance(Context context) {

        if (instance == null) {
            instance = new CommonUtils(context);
        }
        return instance;
    }
}
2.非靜態(tài)內部類引起內存泄漏 (包括靜態(tài)內部類)

錯誤的示范:

        public void loadData(){//隱士持有MainActivity實例位迂。MainActivity.this.a
        new Thread(new Runnable() {
            @Override
            public void run() {
            while(true){
                try {
                //int b=a;
                Thread.sleep(1000);
                } catch (InterruptedException e) {
                e.printStackTrace();
                }
            }
            }
        }).start();
        }
解決方案:
將非靜態(tài)內部類修改為靜態(tài)內部類访雪。
   public static  void loadData(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
在比如用弱引用解決Handler內存泄漏問題
public class MainActivity extends AppCompatActivity {
    int a;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }
    private static class MyHandler extends Handler {

        /**
         * 1.直接持有一個外部類的強引用,會內存泄漏
         */
//        private MainActivity mainActivity;
//        public MyHandler(MainActivity mainActivity) {
//            this.mainActivity = mainActivity;
//        }


        /**
         * 2.使用弱應用
         * WeakReference 是一個弱引用掂林,當所引用的對象在JVM內不再強引用時臣缀,GC后 weak reference 將會被自動回
         */
        private WeakReference<MainActivity> mainActivity;

        public MyHandler(MainActivity mainActivity) {
            this.mainActivity = new WeakReference<MainActivity>(mainActivity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity main = mainActivity.get();
            if (main == null || main.isFinishing()) {
                return;
            }
            switch (msg.what) {
                case 0:
                    //加載外部數據
                    int b = main.a;
                    break;
            }
        }
    }
}

WeakReference --- 弱引用
WeakReference 是一個弱引用, 當所引用的對象在 JVM 內不再有強引用時, GC 后 weak reference 將會被自動回
回收時機:在垃圾回收的時候;使用:同軟引用泻帮; 生命周期:GC后終止

當時用軟應用或者弱應用的時候精置,MainActivity難道很容易或者可以被GC回收嗎?
GC回收的機制是什么锣杂?當MainActivity不被任何的對象引用脂倦。
雖然Handler里面用的是軟引用/弱引用,但是并不意味著不存在其他的對象引用該MainActivity
當MainActivity都被回收了元莫,那他里面的Handler自然沒必要在進行處理赖阻,return 即可。

Android 非靜態(tài)內部類/匿名類引起的內存泄漏
android non-static內部類導致的內存泄露

3.不需要用的監(jiān)聽未移除會發(fā)生內存泄漏

例1

        final TextView tv_test = (TextView) findViewById(R.id.tv_test);
        tv_test.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
            @Override
            public void onWindowFocusChanged(boolean hasFocus) {
                //監(jiān)聽view的加載,view 加載出來的時候踱蠢,計算它的寬高
                // 計算完后火欧,一定要移除這個監(jiān)聽
                tv_test.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
            }
        });

例2

        SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
        SensorEventListener listener = (SensorEventListener) this;
        sensorManager.registerListener(listener,sensor,SensorManager.SENSOR_DELAY_FASTEST);
        sensorManager.unregisterListener(listener);
4.資源未關閉引起的內存泄漏情況

對于使用了BraodcastReceiver,ContentObserver,File苇侵,Cursor离陶,Stream,Bitmap衅檀,自定義屬性attribute等資源的代碼招刨,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收哀军,造成內存泄漏沉眶。

5.無限循環(huán)動畫

沒有在onDestory中停止動畫,否則Activity就會變成泄漏對象杉适。
比如谎倔,輪播圖效果

特別感謝:
動腦學院Ricky

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猿推,隨后出現的幾起案子片习,更是在濱河造成了極大的恐慌,老刑警劉巖蹬叭,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藕咏,死亡現場離奇詭異,居然都是意外死亡秽五,警方通過查閱死者的電腦和手機孽查,發(fā)現死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來坦喘,“玉大人盲再,你說我怎么就攤上這事“晗常” “怎么了答朋?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棠笑。 經常有香客問我梦碗,道長,這世上最難降的妖魔是什么腐晾? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任叉弦,我火速辦了婚禮,結果婚禮上藻糖,老公的妹妹穿的比我還像新娘淹冰。我一直安慰自己,他們只是感情好巨柒,可當我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布樱拴。 她就那樣靜靜地躺著柠衍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪晶乔。 梳的紋絲不亂的頭發(fā)上珍坊,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機與錄音正罢,去河邊找鬼阵漏。 笑死,一個胖子當著我的面吹牛翻具,可吹牛的內容都是我干的履怯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼裆泳,長吁一口氣:“原來是場噩夢啊……” “哼叹洲!你這毒婦竟也來了?” 一聲冷哼從身側響起工禾,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤运提,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闻葵,有當地人在樹林里發(fā)現了一具尸體民泵,經...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年笙隙,在試婚紗的時候發(fā)現自己被綠了洪灯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坎缭。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡竟痰,死狀恐怖,靈堂內的尸體忽然破棺而出掏呼,到底是詐尸還是另有隱情坏快,我是刑警寧澤,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布憎夷,位于F島的核電站莽鸿,受9級特大地震影響,放射性物質發(fā)生泄漏拾给。R本人自食惡果不足惜祥得,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒋得。 院中可真熱鬧级及,春花似錦、人聲如沸额衙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至县踢,卻和暖如春转绷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硼啤。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工议经, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谴返。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓爸业,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亏镰。 傳聞我的和親對象是個殘疾皇子扯旷,可洞房花燭夜當晚...
    茶點故事閱讀 43,435評論 2 348

推薦閱讀更多精彩內容