記憶體洩漏往往面試會問到是否有解決過實際問題,這個如果答不好,也是很容易露餡的,面試時必須得把這艘火箭造好,才有機會進去擰螺絲。其他完整面試專題,請關注公衆号檢視。
1、Java虛拟機記憶體模型
完整内容參考Java記憶體模型

虛拟機棧:線程私有,随線程建立而建立。棧裡面是一個一個“棧幀”,每個棧幀對應一次方法調用。棧幀中存放了局部變量表(基本資料類型變量和對象引用)、操作數棧、方法出口等資訊。當棧調用深度大于JVM所允許的範圍,會抛出StackOverflowError的錯誤。
本地方法棧:線程私有,這部分主要與虛拟機用到的Native方法相關,一般情況下,并不需要關心這部分的内容。
程式計數器:也叫PC寄存器,JVM支援多個線程同時運作,每個線程都有自己的程式計數器。倘若目前執行的是 JVM 的方法,則該寄存器中儲存目前執行指令的位址;倘若執行的是native方法,則PC寄存器中為空。(PS:線程執行過程中并不都是一口氣執行完,有可能在一個CPU時鐘周期内沒有執行完,由于時間片用完了,是以不得不暫停執行,當下一次獲得CPU資源時,通過程式計數器就知道該從什麼地方開始執行)
方法區:方法區存放類的資訊(包括類的位元組碼,類的結構)、常量、靜态變量等。字元串常量池就是在方法區中。雖然Java虛拟機規範把方法區描述為堆的一個邏輯部分,但是它卻有一個别名叫做Non-Heap(非堆),目的是與Java堆區分開來。很多人都更願意把方法區稱為“永久代”(Permanent Generation)。從jdk1.7已經開始準備“去永久代”的規劃,jdk1.7的HotSpot中,已經把原本放在方法區中的靜态變量、字元串常量池等移到堆記憶體中。
堆:堆中存放的是數組(PS:數組也是對象)和對象。當申請不到空間時會抛出OutOfMemoryError。
2、記憶體洩漏原理
Android是基于Java的一門語言,其垃圾回收機制也是基于Jvm建立的,是以說Android的GC也是通過可達性分析算法來判定的。但是如果一個存活時間長的對象持有另一個存活時間短的對象就會導緻存活時間短的對象在GC時被認定可達而不能被及時回收,而繼續停留在堆記憶體中,也就是我們常說的記憶體洩漏。Android對每個App記憶體的使用有着嚴格的限制,大量的記憶體洩漏就可能導緻OOM(記憶體溢出),也就是在new對象請求空間時,堆中沒有剩餘的記憶體配置設定所導緻的。
3、Java引用類型
Java對引用的分類有 StrongReference(預設引用類型), SoftReference, WeakReference,PhatomReference 四種。
4、OOM是否可以try catch?
隻有在一種情況下,這樣做是可行的:
在try語句中聲明了很大的對象,導緻OOM,并且可以确認OOM是由try語句中的對象聲明導緻的,那麼在catch語句中,可以釋放掉這些對象,解決OOM的問題,繼續執行剩餘語句。但是這通常不是合适的做法。
5、finalize()方法
finalize()是Object的protected方法,子類可以覆寫該方法以實作資源清理工作,當對象變成(GC Roots)不可達時,GC會判斷該對象是否覆寫了finalize方法,若未覆寫,則直接将其回收。否則,若對象未執行過finalize方法,将其放入F-Queue隊列,由一低優先級線程執行該隊列中對象的finalize方法。執行finalize方法完畢後,GC會再次判斷該對象是否可達,若不可達,則進行回收,否則,對象“複活”。
finalize方法至多由GC執行一次,即使在finalize()方法中複活對象,第二次GC如果對象仍不可達,那麼還是會被回收。
6、Android檢視記憶體/CPU占用資訊
adb shell dumpsys meminfo pid
adb shell dumpsys cpuinfo|find "包名"
adb shell top
7、Android記憶體洩漏如何定位
使用Android Studio 自帶的AndroidProfiler工具或MAT
使用Square産品的LeakCanary.
詳情:https://www.jianshu.com/p/1972a6d1f0fc
8、LeakCanary原理
監測機制利用了Java的WeakReference和ReferenceQueue,通過将Activity(對象)包裝到WeakReference中,被WeakReference包裝過的Activity對象如果被回收,該WeakReference引用會被放到ReferenceQueue中,通過監測ReferenceQueue裡面的内容,就能檢查到Activity是否能夠被回收。
詳細原理:https://blog.csdn.net/tobetheender/article/details/53609563
9、常見記憶體洩漏的場景
- 集合類洩漏
Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
Objecto = new Object();
v.add(o);
o = null;
}
複制
-
單例造成的記憶體洩漏
由于單例的靜态特性使得其生命周期跟應用的生命周期一樣長,如果單例内部持有Activity/Fragment/View等的引用,會導緻其無法被回收。
-
匿名内部類/非靜态内部類和異步線程
Java中,非靜态内部類和匿名内部類預設會持有外部類的引用,比較容易引起記憶體洩漏的有Handler, AsyncTask使用匿名内部類形式。
-
資源未關閉造成的記憶體洩漏
對于使用了BraodcastReceiver,ContentObserver,File,遊标 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者登出,否則這些資源将不會被回收,造成記憶體洩漏。
-
一些不良代碼造成的記憶體壓力
例如,隻進行注冊而沒有反注冊,構造 Adapter 時,沒有使用緩存的 convertView。
10、避免記憶體洩漏的優化
直接就是針對上面提到的4點進行優化,集合資源add後不用及時remove;Handler使用靜态内部類+弱引用,AsyncTask可以在onDestroy()内調用cancel方法;資源使用完及時進行close擷取release;注冊與反注冊成對出現,Adapter進行convertView複用。
11、一個線程OOM後,其他線程是否還能正常工作?
美團18年三面題目
結合第1題,大家第一反應容易直覺覺得是堆溢出,然後結合第1題堆是線程共享的,是以其他線程也都異常。實際上并非如此,當一個線程抛出OOM異常後,它所占據的記憶體資源會立即全部被釋放掉,進而不會影響其他線程的運作。同理,棧溢出也是一樣的。如果主線程抛異常退出了,子線程也還能運作,除非這些子線程是守護線程,那麼會随着主線程異常結束而結束。