android 內(nèi)存泄露

公司的項目基本該有的功能都做完了次哈,之前都是在趕工,改需求吆录,媽的需求改得還真TM頻繁窑滞,從開始做的時候就改到要發(fā)布,這種情況已經(jīng)持續(xù)一年多了,一直到現(xiàn)在哀卫,害得每次都趕工巨坊,著實是蛋疼,真他媽的不想干了聊训。因為之前都是趕功能抱究,根本就沒時間對app進行內(nèi)存優(yōu)化恢氯,現(xiàn)在沒什么事做了带斑,就來優(yōu)化下內(nèi)存。

對于android手機而言勋拟,內(nèi)存是很寶貴的勋磕,不像PC一樣。手機內(nèi)存本來就不多敢靡,如果開發(fā)上還不注意節(jié)約內(nèi)存的開銷挂滓,很容易導致可用內(nèi)存變得越來越少,到你的app的使用內(nèi)存超過向系統(tǒng)申請內(nèi)存時啸胧,系統(tǒng)就不得不把你的app進程給kill掉赶站。所以我們?yōu)榱瞬唤o系統(tǒng)增加太多的壓力和不讓我們app給系統(tǒng)干掉,還是優(yōu)化下內(nèi)存開銷吧纺念。

  • 什么是內(nèi)存泄露
在java中贝椿,如果一個對象沒有可用價值了,但又被其他引用所指向陷谱,那么這個對象對于gc來說就不是一個垃圾烙博,
所以不會對其進行回收,但是我們認為這應(yīng)該是個垃圾烟逊,應(yīng)該被gc回收的渣窜。這個對象得不到gc的回收,
就會一直存活在堆內(nèi)存中宪躯,占用內(nèi)存乔宿,就跟我們說的霸著茅坑不拉屎的道理是一樣的。這樣就導致了內(nèi)存的泄露访雪。

所以我們在開發(fā)中就應(yīng)該盡量避免內(nèi)存泄露详瑞,讓app使用更加流暢。

  • 內(nèi)存泄露檢測工具

1冬阳、MAT蛤虐,下載地址: MAT ,這個工具功能很強大,但是學習成本比較高肝陪,我用了幾遍就不想用了驳庭,實在是麻煩,每次都要導出.hprof文件,然后通過命令行把.hprof轉(zhuǎn)換成MAT可以識別的文件饲常,里面的功能也要學習斷時間才行蹲堂。
2、LeakCanary贝淤, 下載地址:LeakCanary ,這個工具實在是太刁了柒竞,方便,使用簡單播聪,在通知欄通知內(nèi)存泄露朽基,我非常喜歡。以下講解下他的使用离陶,當然稼虎,你也可以看官方文檔。

1.1招刨、首先我們在build.gradle中的dependencies中添加以下代碼

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' // or 1.4-beta1
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // or 1.4-beta1

debugCompile 是在調(diào)試的時候使用的霎俩,releaseCompile 是在release.apk包中不使用的,這樣配置也就不用我們修改任何代碼了沉眶,我們正式打的包是不會出現(xiàn)leakcanary那些提示的打却。

1.2、在Application中的onCreat方法添加代碼:

LeakCanary.install(this);

經(jīng)過以上配置就可以監(jiān)聽activity是否存在內(nèi)存泄露了谎倔,什么代碼都不用加了柳击。如果我們需要監(jiān)聽某個對象是否存在內(nèi)存泄露,我們可以這樣做:

RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); 
refWatcher.watch(testInstance);
  • 內(nèi)存泄露案例1
public class LeakActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        },1000 * 60);
    }
}

以上代碼很簡單传藏,handler在post了一個1分鐘后執(zhí)行的runnable腻暮,而這個runnable是掛載在主線程的√赫欤看似很平常哭靖,沒什么不對,我們運行程序后侈离,可以看到以下截圖:

device-2016-02-26-171945.png

從上圖我們可以看到试幽,LeakActivity的對象發(fā)生了內(nèi)存泄露,是由mMessageQueue這個對象導致的卦碾。當我們啟動LeakActivity的時候铺坞,handler會把Runnable對象封裝成一個Message,然后 post 這個Message進MessageQueue,等待60秒后執(zhí)行。我們結(jié)束LeakActivity時洲胖,并沒有取消掉MessageQueue里的Message济榨,所以Message里的Runnable會一直等到1分鐘結(jié)束后執(zhí)行里面的run方法绿映,在這過程中MessageQueue會一直持有Message的對象引用腐晾,然而Runnable是封裝在這個Message的丐一,所以他們之間的引用關(guān)系就像這樣:MessageQueue->Message->Runnable->Handler->Activity藻糖。所以這就導致當前activity與MessageQueue一直有關(guān)聯(lián),導致LeakActivity的對象不能被gc回收库车,從而導致內(nèi)存泄露。(關(guān)于Handler柠衍、Message、MessageQueue拧略、Looper之間的工作原理可以去看下源碼)

所以要避免上面的內(nèi)存泄露芦岂,我們可以這樣做瘪弓,在activity的onDestroy方法中干掉handler的所有callback和message:

@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}

這樣就ok了垫蛆。

在activity中開啟的線程也是一樣腺怯,如果activity結(jié)束了而線程還在跑,一樣會導致activity內(nèi)存泄露呛占,因為"非靜態(tài)內(nèi)部類對象都會持有一個外部類對象的引用"晾虑,你創(chuàng)建的線程就是activity中的一個內(nèi)部類,持有activity對象的引用帜篇,當activity結(jié)束了,但線程還在跑笙隙,就會導致activity內(nèi)存泄露。

上面的例子是很容易看出是否有內(nèi)存泄露签钩,那么接下來的例子就沒那么容易看出來了坏快,而且開發(fā)中使用的頻率是很高的。

  • 內(nèi)存泄露案例2

LeakActivity1

public class LeakActivity1 extends AppCompatActivity {

    private TestManager testManager = TestManager.getInstance();
    private MyListener listener=new MyListener() {
        @Override
        public void doSomeThing() {}
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        testManager.registerListener(listener);
    }

}

TestManager

public class TestManager {

    private TestManager(){}
    private static final TestManager INSTANCE = new TestManager();
    private MyListener listener;

    public static TestManager getInstance() {
        return INSTANCE;
    }

    public void registerListener(MyListener listener) {
        this.listener = listener;
    }
    public void unregisterListener() {
        listener = null;
    }
}

interface MyListener {
    void doSomeThing();
}

運行LeakActivity1后出現(xiàn)了內(nèi)存泄露昧旨,如下圖:

device-2016-02-26-183138.png

由上圖我們可以看出,LeakActivity1還是內(nèi)存泄露了鸣戴。我按照上面標注的順序說下粘拾,第1個中的leaks是內(nèi)存泄露的意思,代表是LeakActivity1的對象instance內(nèi)存泄露了缰雇,2中references是引用的意思入偷,導致內(nèi)存泄露是由LeakActivity1中的一個實現(xiàn)了MyListener的匿名類導致的疏之,這里的引用就是指這個實現(xiàn)類的引用暇咆,3中的reference是指TestManager類中的listener這個引用,4中指出了最終導致內(nèi)存泄露的根本源頭為TestManger類中的INSTANCE爸业。

上面的代碼中,我們定義了個TestManager,并且使用單例模式拯爽,然后在activity實現(xiàn)MyListener接口钧忽,再通過testManager.registerListener(listener);注冊個回調(diào)。我們開發(fā)中很經(jīng)常這樣干耸黑。但是我們這里的是單例,如下圖备禀,當程序執(zhí)行LeakActivity1時奈揍,讀到private TestManager testManager=TestManager.getInstance(); 這句代碼時,首先會在棧內(nèi)存中開辟內(nèi)存存儲testManager變量另患,然后讀到TestManager.getInstance();時候蛾绎,會加載TestManager類鸦列,因為TestManager中有static對象鹏倘,static跟類的生命周期是一樣的,類一加載纤泵,static就加載了,類一被銷毀玻褪,static才會跟著銷毀(static是存在方法區(qū))公荧,這時候jvm會在方法區(qū)中存儲變量INSTANCE,然后在堆內(nèi)存開辟空間存放INSTANCE對象窟社,然后把地址值付給INSTANCE變量晤揣,使INSTANCE變量就指向這個對象(類似c語言的指針),activity類也是這樣的一種執(zhí)行關(guān)系昧识。

內(nèi)存中的加載情況.png

因為這是一個單例跪楞,當app進程被干掉的時候侣灶,堆內(nèi)存中的INSTANCE對象才會被釋放,所以INSTANCE對象的生命周期是很長的褥影,LeakActivity1中凡怎,listener持有當前activity的對象,然后testManager.registerListener(listener);執(zhí)行完统倒,TestManager中的listener就持有activity中l(wèi)istener的對象,而TestManager中的INSTANCE是static的耸成,生命周期長,activity銷毀的時候INSTANCE依然還在井氢,INSTANCE還在,那么TestManager類中的全局變量也還是存在的骗卜,所以TestManager中的listener變量還在左胞,還一直持有LeakActivity1中的listener對象引用,所以最終是INSTANCE導致LeakActivity1內(nèi)存泄露遍烦。

所以躺枕,要解決這個問題,可以這樣做拐云,在activity的onDestroy方法中注銷注冊的listener:

@Override
protected void onDestroy() {
    testManager.unregisterListener();
    super.onDestroy();
}

這樣做后TestManager中的listener不再持有LeakActivity1中的listener對象引用叉瘩,所以LeakActivity1被銷毀后listener對象也可被回收了。
最終薇缅,問題又解決了,當然你也可以直接把INSTANCE置null汤徽。

  • 總結(jié)
1灸撰、小心使用static
2、線程生命周期要跟activity同步
3浮毯、小心使用第三方j(luò)ar包(我開發(fā)中就遇到過jar包中持有activity對象導致的內(nèi)存泄露)
4亲轨、網(wǎng)絡(luò)請求也是線程操作的,也應(yīng)該與activity生命周期同步惦蚊,在onDestroy的時候cancle掉請求
5讯嫂、盡量使用application代替activity和context:  Context context = activity.getApplication();這樣就使得context不是指向Activity了兆沙,指向全局的application,這樣就沒內(nèi)存泄露可說了千扔。
等等......

不單單是activity會出現(xiàn)內(nèi)存泄漏的库正,其他的類對象也可能會泄漏,對象回收不了龙誊,那么類中的其他變量值也會在喷楣,如果不處理,量一多铣焊,還是挺可怕的。今天就寫到這了叽讳,公司現(xiàn)在就剩我一個人了熊昌,該回去吃飯了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市昂利,隨后出現(xiàn)的幾起案子铁坎,更是在濱河造成了極大的恐慌,老刑警劉巖扩所,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朴乖,死亡現(xiàn)場離奇詭異助赞,居然都是意外死亡袁勺,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門群叶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钝荡,“玉大人,你說我怎么就攤上這事赎离≈惨酰” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵憾朴,是天一觀的道長喷鸽。 經(jīng)常有香客問我,道長砾省,這世上最難降的妖魔是什么混槐? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮狠鸳,結(jié)果婚禮上悯嗓,老公的妹妹穿的比我還像新娘。我一直安慰自己脯厨,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布临梗。 她就那樣靜靜地躺著壳澳,像睡著了一般茫经。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抹镊,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天荤傲,我揣著相機與錄音,去河邊找鬼终佛。 笑死雾家,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的芯咧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼邪铲,長吁一口氣:“原來是場噩夢啊……” “哼带到!你這毒婦竟也來了英染?” 一聲冷哼從身側(cè)響起阴孟,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箭养,沒想到半個月后哥牍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喝检,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡撼泛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年愿题,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潘酗。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡仔夺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缸兔,到底是詐尸還是另有隱情,我是刑警寧澤昂拂,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布蝎抽,位于F島的核電站,受9級特大地震影響养交,放射性物質(zhì)發(fā)生泄漏瓢宦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一鱼辙、第九天 我趴在偏房一處隱蔽的房頂上張望玫镐。 院中可真熱鬧,春花似錦恐似、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阳仔。三九已至扣泊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間旷赖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工稚照, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留俯萌,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓弱恒,卻偏偏與公主長得像棋恼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子义起,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 對于內(nèi)存泄漏师崎,我想大家在開發(fā)中肯定都遇到過,只不過內(nèi)存泄漏對我們來說并不是可見的齐蔽,因為它是在堆中活動床估,而要想...
    EsonJack閱讀 892評論 1 3
  • 內(nèi)存泄露指的是該釋放的對象沒有釋放,一直被某個或某些實例特持有卻不再被使用導致GC不能回收蛙吏。首先,我們先看看Jav...
    PeOS閱讀 680評論 0 2
  • 我的博客:http://xuyushi.github.io原文地址 [TOC] 內(nèi)存泄露 內(nèi)存泄露的定義:當某些對...
    接地氣的二呆閱讀 1,294評論 2 23
  • 參考內(nèi)存泄露從入門到精通三部曲之基礎(chǔ)知識篇Android 內(nèi)存泄漏總結(jié)Android內(nèi)存泄漏研究Android內(nèi)存...
    合肥黑閱讀 437評論 0 3
  • 注意Activity的泄漏 通常來說谓着,Activity的泄漏是內(nèi)存泄漏里面最嚴重的問題,它占用的內(nèi)存多赊锚,影響面廣,...
    738bc070cd74閱讀 580評論 0 2