?很多開發(fā)者都知道,在面試的時候會經(jīng)常被問到內(nèi)存泄露和內(nèi)存溢出的問題刺桃。
內(nèi)存溢出(Out Of Memory粹淋,簡稱 OOM)吸祟,通俗理解就是內(nèi)存不夠瑟慈,即內(nèi)存占用超出內(nèi)存的空間大小桃移。
內(nèi)存泄漏(Memory Leak),簡單理解就是內(nèi)存使用完畢之后本該垃圾回收卻未被回收葛碧。
在正式了解內(nèi)存泄露之前借杰,首先來簡單回顧一下 Java 內(nèi)存分配策略。
Java 程序運行時的內(nèi)存分配策略有三種,分別是靜態(tài)分配进泼、棧式分配蔗衡、堆式分配,對應(yīng)的主要內(nèi)存空間分別是靜態(tài)存儲區(qū)(也稱方法區(qū))乳绕、棧區(qū)绞惦、堆區(qū)。
靜態(tài)存儲區(qū)(方法區(qū))
主要存放靜態(tài)數(shù)據(jù)洋措、全局 static 數(shù)據(jù)和常量济蝉。這塊內(nèi)存在程序編譯時就已經(jīng)分配好,并且在程序整個運行期間都存在菠发。
棧區(qū)
當方法被執(zhí)行時王滤,方法體內(nèi)的局部變量都在棧上創(chuàng)建,并在方法執(zhí)行結(jié)束時這些局部變量所持有的內(nèi)存將會自動被釋放滓鸠。因為棧內(nèi)存分配運算內(nèi)置于處理器的指令集中雁乡,效率很高,但是分配的內(nèi)存容量有限糜俗。
堆區(qū)
又稱動態(tài)內(nèi)存分配踱稍,通常就是指在程序運行時直接 new 出來的內(nèi)存。這部分內(nèi)存在不使用時將會由 Java 垃圾回收器來負責回收悠抹。
在 Java 中寞射,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由 GC 完成的锌钮,這種方式大大簡化了程序員的工作桥温,但同時卻加重了 JVM 的工作,這也是 Java 程序運行速度較慢的原因之一梁丘。GC 為了能夠正確釋放對象侵浸,必須監(jiān)控每一個對象的運行狀態(tài),包括對象的申請氛谜、引用掏觉、被引用、賦值等值漫,監(jiān)視對象的狀態(tài)是為了更加準確地澳腹、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。
Java 有了垃圾回收功能酱塔,程序員無需手動管理內(nèi)存分配沥邻,減少了段錯誤導致的閃退,也減少了內(nèi)存泄漏導致的堆空間膨脹羊娃,讓編寫的代碼更加安全唐全。但是 Java 中依然有可能發(fā)生內(nèi)存泄露,而 Android 主要使用 Java 作為開發(fā)語言蕊玷,在開發(fā)過程中很可能一個很小的錯誤都會引起內(nèi)存的泄露邮利。有內(nèi)存泄露存在時,APP 就會浪費大量的內(nèi)存垃帅,就會由于內(nèi)存不夠而頻繁進行垃圾回收延届,大家知道垃圾回收是非常耗時的操作,這樣就會導致 APP 的嚴重卡頓贸诚。在最壞的時候方庭,甚至由于內(nèi)存耗盡導致OutOfMemery,最終程序異常退出赦颇。
內(nèi)存泄露是一個令很多開發(fā)者頭疼的問題二鳄,那么我們今天就來學習一下 Activity 有關(guān)的內(nèi)存泄露問題。
在 Android 中媒怯,泄露 Context 對象的問題尤其嚴重订讼,特別像 Activity 這樣的 Context 對象會引用大量很占用內(nèi)存的對象,如果 Context 對象發(fā)生了內(nèi)存泄漏扇苞,那它所引用的所有對象都被泄漏了欺殿。Activity 是非常重量級的對象,所以我們應(yīng)該極力避免妨礙系統(tǒng)對其進行回收鳖敷,然而實際情況是有多種方式會無意間就泄露了Activity 對象脖苏。
1. 靜態(tài)變量造成的內(nèi)存泄漏
最簡單的泄漏 Activity 就是在 Activity 類中定義一個 static 變量,并將其指向一個運行中的 Activity 實例定踱。如果在 Activity 的生命周期結(jié)束之前棍潘,沒有清除這個引用,那它就會泄漏崖媚。由于 Activity 的類對象是靜態(tài)的亦歉,一旦加載,就會在 APP 運行時一直常駐內(nèi)存畅哑,如果類對象不卸載肴楷,其靜態(tài)成員就不會被垃圾回收。
2. 單例造成的內(nèi)存泄漏
另一種類似的情況是對經(jīng)常啟動的 Activity 實現(xiàn)一個單例模式荠呐,讓其常駐內(nèi)存可以使它能夠快速恢復(fù)狀態(tài)赛蔫。
如我們有一個創(chuàng)建起來非常耗時的 View砂客,在同一個 Activity 不同的生命周期中都保持不變呢,就為它實現(xiàn)一個單例模式呵恢。一旦 View 被加載到界面中鞠值,它就會持有 Context 的強引用,也就是我們的 Activity 對象瑰剃。
由于我們是通過一個靜態(tài)成員引用了這個 View齿诉,所以我們也就引用了 Activity筝野,因此 Activity 就發(fā)生了泄漏晌姚。所以一定不要把加載的 View 賦值給靜態(tài)變量,如果你真的需要歇竟,那一定要確保在 Activity 銷毀之前將其從 View 層級中移除挥唠。
3. 內(nèi)部類造成的內(nèi)存泄漏
我們經(jīng)常在 Activity 內(nèi)部定義一個內(nèi)部類,這樣做可以增加封裝性和可讀性焕议。但是如果當我們創(chuàng)建了一個內(nèi)部類的對象宝磨,并通過靜態(tài)變量持有了 Activity 的引用,那也會可能發(fā)生 Activity 泄漏盅安。
4. 線程造成的內(nèi)存泄漏
在 Activity 內(nèi)定義了一個匿名的 AsyncTask 對象唤锉,就有可能發(fā)生內(nèi)存泄漏。如果 Activity 被銷毀之后 AsyncTask 仍然在執(zhí)行别瞭,那就會阻止垃圾回收器回收Activity 對象窿祥,進而導致內(nèi)存泄漏,直到執(zhí)行結(jié)束才能回收 Activity蝙寨。
同樣的晒衩,使用 Thread 和 TimerTask 也可能導致 Activity 泄漏。只要它們是通過匿名類創(chuàng)建的墙歪,盡管它們在單獨的線程被執(zhí)行听系,它們也會持有對 Activity 的強引用,進而導致內(nèi)存泄漏虹菲。
5.Handler 造成的內(nèi)存泄漏
定義一個匿名的 Runnable 對象并將其提交到 Handler 上也可能導致 Activity 泄漏靠胜。Runnable 對象間接地引用了定義它的 Activity 對象,而它會被提交到Handler 的 MessageQueue 中毕源,如果它在 Activity 銷毀時還沒有被處理浪漠,就會導致 Activity 泄漏。
6. 資源未關(guān)閉造成的內(nèi)存泄漏
如系統(tǒng)服務(wù)可以通過 context.getSystemService 獲取脑豹,它們負責執(zhí)行某些后臺任務(wù)郑藏,或者為硬件訪問提供接口。如果 Context 對象想要在服務(wù)內(nèi)部的事件發(fā)生時被通知瘩欺,那就需要把自己注冊到服務(wù)的監(jiān)聽器中必盖。然而拌牲,這會讓服務(wù)持有 Activity 的引用,如果開發(fā)者忘記在 Activity 銷毀時取消注冊歌粥,也會導致 Activity泄漏塌忽。
這里只是簡單的分析了常見的有關(guān) Activity 導致的內(nèi)存泄露,其實導致內(nèi)存泄露的地方還有很多失驶,但是基本原理都一樣土居。由于篇幅原因,這里不做過多介紹嬉探,以后再逐漸進行分析和學習擦耀。
雖然現(xiàn)在手機內(nèi)存越來越大,內(nèi)存泄露不會像以前由于內(nèi)存過小造成 OOM涩堤。但是過量的內(nèi)存泄露依然會造成內(nèi)存溢出眷蜓,影響用戶體驗,所以解決好內(nèi)存泄露的問題非常重要胎围。
今天就先分享到這里吁系,后續(xù)將推出更多精彩內(nèi)容,歡迎一起探討學習進步白魂。
此文章為分享達人秀(ShareExpert)——鑫鱻原創(chuàng)汽纤,若轉(zhuǎn)載請備注出處,特此聲明福荸!