上一節(jié)有介紹了一些和內存相關的基礎知識滚婉,這一節(jié)就講一下怎么發(fā)現(xiàn)和處理內存問題长豁。對于我們來說进苍,最容易發(fā)現(xiàn)的內存問題當然是OOM(OutOfMemoryError)癣蟋,應用直接Crash痢掠,日志也會很清晰的標明哪個對象OOM了谢肾。這個解決起來也不難剥悟,常見的Bitmap OOM相信大家也知道怎么處理黔漂。
相對于OOM較麻煩一點的就是內存泄露(Memory Leak)车海,每次就露那么一點笛园,就像溫水煮青蛙一樣,很難發(fā)現(xiàn)有變化侍芝。內存泄漏也是造成應用程序OOM的主要原因之一研铆!我們知道Android系統(tǒng)為每個應用程序分配的內存有限,而當一個應用中產(chǎn)生的內存泄漏比較多時州叠,之后我們再申請新的內存時會及其容易產(chǎn)生OOM棵红。
內存泄漏指的是進程中某些對象(垃圾對象)已經(jīng)沒有使用價值了,但是它們卻可以直接或間接地引用到GC roots導致無法被GC回收咧栗。無用的對象占據(jù)著內存空間逆甜,使得實際可使用內存變小虱肄,形象地說法就是內存泄漏了。
面試題:如何檢測內存泄露交煞,如何進行內存優(yōu)化咏窿?
Android系統(tǒng)為每一個應用程序都設置了一個硬性的Dalvik Heap Size最大限制閾值,這個閾值在不同的設備上會因為RAM大小不同而各有差異素征。如果你的應用占用內存空間已經(jīng)接近這個閾值集嵌,此時再嘗試分配內存的話,很容易引起OOM御毅。
較簡單的查看一個應用的內存使用情況可以通過DDMS的Heap視圖查看:
在Android Studio上也可以通過Memory Monitor查看內存中Dalvik Heap的實時變化:
注意:GC過于頻繁容易出現(xiàn)內存抖動根欧,這也是造成應用卡頓的常見原因。
也可以通過命行的方式查看:
adb shell dumpsys meminfo <package_name|pid> [-d]
具體的數(shù)值意義可以查看官網(wǎng)的說明:https://developer.android.com/studio/profile/investigate-ram.html
MAT內存分析工具
詳細的內存使用情況端蛆,可以通過Android Studio的Android Monitor界面凤粗,在Memory那欄有上幾個小圖標,點擊有一個向下箭頭的圖標會自動生成并打開的HPROF視圖今豆。
不過用他來分析內存泄露還不是很智能嫌拣,我們可以借助第三方工具,常見的工具就是MAT了(Memory Analyzer Tool)呆躲,下載地址 http://eclipse.org/mat/downloads.php亭罪,這里我們需要下載獨立版的MAT(之前在使用Ecelipse開發(fā)Android應用時,我們常常會使用它的插件版本)歼秽。
注意:Android Monitor生成的HPROF文件為Dalvik虛擬機格式的,需要轉成J2SE虛擬機格式的情组,否則MAT工具中無法打開燥筷。轉換的方式也很簡單,Android Studio自帶了院崇,直接在“Captures”->"Heap Snapshot"選中剛剛生成的".hprof"文件肆氓,然后鼠標右鍵選擇“Export to standard .hprof”可以在MAT上使用了。
MAT的具體使用方式底瓣,網(wǎng)上很多谢揪,大家可以自己搜一下。這里就提一下用它怎么能快速查找到內存泄露的點捐凭,比如通過“Dominator Tree”的"Path To GC Roots"的排除虛引用/弱引用/軟引用等的引用鏈拨扶,因為被虛引用/弱引用/軟引用的對象可以直接被GC給回收,我們要看的就是某個我們已經(jīng)不需要使用的對象否還存在強引用鏈茁肠。比如患民,我們已退出一個Activity(onDestroy方法也被執(zhí)行了),但在Path To GC Roots中卻發(fā)現(xiàn)這個Activity對象還被有一個引用鏈垦梆,那么就可以確認這個Activity對像就產(chǎn)生了內存泄漏匹颤。一般來說仅孩,從它的引用鏈上也可以直觀地看出是誰在引用它。
除了上面介紹了MAT檢測內存泄露, 有一個叫LeakCanary工具大家也可以嘗試一下印蓖。項目地址:https://github.com/square/leakcanaryLeakCanary會檢測應用的內存回收情況辽慕,如果發(fā)現(xiàn)有垃圾對象沒有被回收,就會去分析當前的內存快照赦肃,也就是上邊MAT用到的.hprof文件溅蛉,找到對象的引用鏈,并顯示在頁面上摆尝。這款插件的好處就是温艇,可以在手機端直接查看內存泄露的地方,可以輔助我們檢測內存泄露堕汞。
開發(fā)中如何避免內存泄漏
這點我比較喜歡問面試者勺爱,希望面試者能羅列出一些他自己遇到過的情況。通常來說讯检,Activity的泄漏是內存泄漏里面最嚴重的問題琐鲁,它占用的內存多(它里面有N多資源的引用),影響比較明顯人灼。下面就示例兩種錯誤的引用方式围段。
錯誤的單例模式
public class Singleton {
private static Singleton instance;
private Context mContext;
private Singleton(Context context) {
this.mContext = context;
}
public static Singleton getInstance(Context context) {
if (instance == null) {
instance = new Singleton(context);
}
return instance;
}
}
這是一個非線程安全的單例模式,instance作為靜態(tài)對象投放,其生命周期要長于普通的對象奈泪,其中也包含Activity,假如Activity A去getInstance獲得instance對象灸芳,傳入this涝桅,常駐內存的Singleton保存了你傳入的Activity A對象,并一直持有烙样,即使Activity被銷毀掉冯遂,但因為它的引用還存在于一個Singleton中,就不可能被GC掉谒获,這樣就導致了內存泄漏蛤肌。
View持有Activity引用
public class MainActivity extends Activity {
private static Drawable mDrawable;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = new ImageView(this);
mDrawable = getResources().getDrawable(R.drawable.ic_launcher);
iv.setImageDrawable(mDrawable);
}
}
有一個靜態(tài)的Drawable對象當ImageView設置這個Drawable時,ImageView保存了mDrawable的引用批狱,而ImageView傳入的this是MainActivity的mContext裸准,因為被static修飾的mDrawable是常駐內存的,MainActivity是它的間接引用赔硫,MainActivity被銷毀時狼速,也不能被GC掉,所以造成內存泄漏。
其實避免Activity的泄漏的方式可以總結為:不要讓生命周期長于Activity的對象持有到Activity的引用向胡。
在開發(fā)中恼蓬,我們也可以給一些初級的工程師相關的建議,如:
- 注意單例模式和靜態(tài)變量是否會持有對Context的引用僵芹;
- 注意監(jiān)聽器的注銷处硬;(在Android程序里面存在很多需要register與unregister的監(jiān)聽器,我們需要確保在合適的時候及時unregister那些監(jiān)聽器拇派。)
- 不要在Thread或AsyncTask中的引用Activity荷辕;
小結
內存泄漏檢測并不屬于一個經(jīng)常會做的事情,所以上面寫的一些東西難免會有一些錯誤件豌。不過我認為在面試中疮方,更關注的是面試者做何去發(fā)現(xiàn)和解決這個問題,然后是否會對遇到過的問題有一個總結茧彤,至于細節(jié)上的東西在真正做的時候會直接得到工具或LOG的反饋骡显,不一定非常記得很清楚的人才說明他會這個東西。