本文轉自:https://www.testwo.com/article/1153
1、前言
Hello,小伙伴們,相信大家在項目測試中都遇到過內存泄露問題,小編也着實爬過很多坑。比如小編所測項目,更換了多實例版本的sdk,橫豎屏切換后有MapView沒有銷毀,導致內存泄露。小編測試手表項目,因為手表內存有限,測試中常遇到應用無響應或者閃退,故而小編對GC機制進行了進一步學習了解。
本文先對Android內存垃圾回收機制進行介紹,之后對分析、定位內存泄露常用的測試方法進行總結,分享給大家。
2、Android內存垃圾回收(GC機制)
2.1綜述
Android 應用中默認有三個線程:“main”主線程、GC線程、和Heap線程,而且在GC線程運行的過程中,主線程會中斷執行。Java程序與C/C++等原生程序的一個不同點就是,Java虛擬機在運行Java程序的過程中,可以自動回收不再使用的對象實例,從而避免了程序員人工管理內存的繁瑣工作。如果設備是單核CPU設備,一次只能運行一個線程,因此在GC線程運行的時候,必須中斷主線程。但是如果設備上有多核CPU,即主線程可以和GC線程同時運行,在這種情況下執行GC,會不會中斷主線程呢?答案是會的。
雖然有不同的內存垃圾回收實現算法,但有些算法需要中斷其他Java線程的執行,如果中斷的時間過長,給用戶的感覺就是應用的響應速度變的越來越慢,甚至有可能出現ANR錯誤。
2.2Android內存管理原理
2.2.1垃圾內存回收算法
常見的垃圾回收算法引用計數法、標注並清理、拷貝、逐代回收,其中android系統采用的是標注並清理和拷貝GC,並不是大多數JVM實現中采用的逐代回收算法。在很多垃圾回收實現中,常常可以看到將幾種算法合並使用的場景。
2.2.2Logcat中的GC信息
Logcat中GC輸出的信息格式如下:
Dalvik虛擬機的Log信息
在Davlik虛擬機(非ART)中,每一次垃圾回收都會返回一條類似的信息。例子如下:
D/dalvikvm( 9050): GC_CONCURRENT freed2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
它們大致可以分成如下幾個部分:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
[GC的原因][回收的內存總量][GC堆內存的統計信息][外部內存的統計信息][中斷時間]
各個字段的含義如下:
(1)、GC的原因,也就是GC類別,可分為:
-
GC_FOR_MALLOC 表示內存垃圾回收過程是因為在分配內存空間如(創建對象)時,內存不夠而引發的。系統會殺死應用的進程並且回收所有內存。
-
GC_CONCURRENT 表明GC是在內存使用率達到一定的警戒線時,自動觸發的。
-
GC_EXPLICIT 表明GC是被顯式請求觸發的。
-
GC_EXTERNAL_ALLOC在API版本10(Android3.0)以下的時候的垃圾回收機制。3.0以上版本所有的內存都在Dalvik堆中分配。它是用來回收dalvik虛擬機以外的內存(例如Bitmap中的內存或者NIObuffer中的內存)。
-
GC_HPROF_DUMP_HEAP 當請求生成HPROF文件來分析內存的時候會觸發此類垃圾回收
(2)、回收的內存總量
(3)、回收后GC內存對的統計信息
堆中可用空間所占的百分比和(堆中對象的數量)/(堆的大小)
(4)、外部內存的統計信息
系統API版本10以下的系統中,Dalvik虛擬機堆外(分配的內存)/(限制的內存量)
(5)、GC造成的應用其他線程中斷的時間
Concurrent類型的垃圾回收有兩次暫停時間:一次發生在開始,另一次發生在結束。堆的內容越多,暫停的時間越長。
觀察這些Log信息,如果heapstats中的數值(堆中對象數量)/(堆的大小)越來越大,那么應用中很有可能存在內存泄漏。
(6)、總結
一般根據下面兩個線索判斷應用是否存在內存泄露問題
1、應用運行一段時間后,因為內部拋出java.lang.OutOfMemoryError異常而崩潰;
2、在logcat中看到頻繁的GC信息;
3、內存泄露
3.1什么是內存泄漏
對於不同的語言平台來說,進行標記回收內存的算法是不一樣的,像Android(Java)則采用GC-Root的標記回收算法。下圖(Google 2011的IO大會)展示了Android內存的回收管理策略。
圖中的每個圓節點代表對象的內存資源,箭頭代表可達路徑。當圓節點與GC Roots存在可達路徑時,表示當前資源正被引用,虛擬機是無法對其進行回收的(如圖中的黃色節點)。反過來,如果圓節點與GC Roots不存在可達路徑,則意味着這塊對象的內存資源不再被程序引用,系統虛擬機可以在GC過程中將其回收掉。
有了上面的內存回收的栗子,那么接下來就可以說說什么是內存泄漏了。
從定義上講,Android(Java)平台的內存泄漏是指沒有用的對象資源任與GC-Root保持可達路徑,導致系統無法進行回收。舉一個最簡單的栗子,我們在Activity的onCreate函數中注冊一個廣播接收者,但是在onDestory函數中並沒有執行反注冊,當Activity被finish掉時,Activity對象已經走完了自身的生命周期,應該被資源回收釋放掉,但由於沒有反注冊,此時Activity和GC-Root間仍然有可達路徑存在,導致Activity雖然被銷毀,但是所占用的內存資源卻無法被回收掉。
3.2泄漏的源頭
這里,將其歸位以下三類:
(1). 自身編碼引起
由項目開發人員自身的編碼造成。
(2). 第三方代碼引起
這里的第三方代碼包含兩類:第三方非開源的SDK和開源的第三方框架。
(3). 系統原因
由Android系統自身造成的泄漏,如像WebView、InputMethodManager等引起的問題,還有某些第三方ROM存在的問題。
3.3泄漏的定位
內存泄漏不像閃退的BUG,排查起來相對要比較困難些,比較極端的情況是當應用OOM了才發現存在內存泄漏問題,對用戶影響太大。為此,我們希望在測試過程中能夠盡早發現問題。下面介紹幾種分析內存泄露問題的工具、方法。
3.3.1靜態代碼分析工具 —— Lint
Lint是 Android Studio自帶的工具,使用姿勢很簡單Analyze -> Inspect Code然后選擇想要掃面的區域即可。
對可能引起泄漏的編碼,Lint都會進行溫馨提示。
關於Lint,大家可以自行拓展學習。
3.3.2Android Memory Monitor
Android Studio提供的工具,用於監控應用的內存使用狀態,在開發中也是非常實用的工具,可以用來打印出內存的狀態信息。
打印獲得的內存信息如下,可以通過右上角的綠色三角形按鈕去分析泄漏的Activity和一些重復的字符串,目前只支持這兩個,希望Google后面能夠加入更多可選分析規則。
3.3.3adb shell 命令
使用 adb shell dumpsys meminfo [PackageName],可以打印出指定包名的應用內存信息。
使用該命令可以很直觀的觀察到Activity的泄漏問題,是平常分析比較常用的一種方式。除了使用命令外,Android Studio也提供了下面的功能,和使用命令是一樣效果的。
以上就是我在做內存泄漏定位分析的時候會用到的工具和方法,通常都是結合起來用,使用多個工具互補分析問題可以提高我們的效率和最終取得的效果。
以上,就是小編做內存泄漏分析的一些心得總結,如有錯誤和不足,還請大家指出。如果大家針對內存泄露測試、分析,還有什么建議或者心得,歡迎留言、一起探討~~O(∩_∩)O~~