Android進程&內存管理及內存泄露簡析
Android進程&內存管理及內存泄露簡析
前言
眾所周知,Android是基於Linux的自由及開放源代碼的操作系統,所以了解android內存前需要先了解下Linux。在Android系統中內存使用和進程調度管理息息相關,而內存使用不當將會造成OOM(Out Of Memory)。所以我們以linux內存管理,Android內存&進程管理,內存泄露三個方面來簡單了解下Android內存管理機制。
一、Linux內存管理優勢
在Linux中經常發現空閑內存很少,似乎所有的內存都被系統佔用了,表面感覺是內存不夠用了,其實不然。這是Linux內存管理的一個優秀特性,在這方 面,區別於Windows的內存管理。主要特點是,無論物理內存有多大,Linux都將其充份利用,將一些程序調用過的硬碟數據讀入內存,利用內存讀寫的高速特性來提高Linux系統的數據訪問性能。而Windows是只在需要內存時才為應用程序分配內存,並不能充分利用大容量的內存空間。換句話說,每增加一些物理內存,Linux都將能充分利用起來,發揮了硬體投資帶來的好處,而Windows只將其做為擺設,即使增加8GB甚至更大。
Linux的內存管理採取的是分頁存取機制,為了保證物理內存能得到充分的利用,內核會在適當的時候將物理內存中不經常使用的數據塊自動交換到虛擬內存中,而將經常使用的信息保留到物理內存。
二、Android內存&進程管理
了解進程管理之前,需要了解一些基本的內容,一台安卓手機有很多應用,而Android系統為每一個進程(應用)分配一個虛擬機,(在4.4以前為Dalvik虛擬機之後變為ART虛擬機),每一個進程都獨立的運行於自己的內存空間中。在了解進程調度之前需要了解下進程的分類和內存的意義,以及進程運行時候所需的空間與手機RAM的關係。
1.Android內存的意義
Android上的應用是Java,在系統運行需要虛擬機,而android上的應用是帶有獨立虛擬機的,也就是每開一個應用就會打開一個獨立的虛擬機。其實和java的垃圾回收機制類似,系統有一個規則來回收內存。進行內存調度有個閥值,只有低於這個值系統才會按一個列表來關閉用戶不需要的東西。當然這個值默認設置得很小,所以你會看到內存老在很少的數值徘徊。但事實上他並不影響速度。相反加快了下次啟動應用的速度。這本來就是android標榜的優勢之一,如果人為去關閉進程,沒有太大必要。特別是使用自動關進程的軟體。為什麼內存少的時候運行大型程序會慢呢,原因是:在內存剩餘不多時打開大型程序時會觸發系統自身的調進程調度策略,這是十分消耗系統資源的操作,特別是在一個程序頻繁向系統申請內存的時候。這種情況下系統並不會關閉所有打開的進程,而是選擇性關閉,頻繁的調度自然會拖慢系統。
2.Android進程的分類
1)前台進程(foreground)
目前正在屏幕上顯示的進程和一些系統進程。舉例來說,Dialer,Storage,Google Search等系統進程就是前台進程;再舉例來說,當你運行一個程序,如瀏覽器,當瀏覽器界面在前台顯示時,瀏覽器屬於前台進程(foreground),但一旦你按home回到主界面,瀏覽器就變成了後台程序(background)。我們最不希望終止的進程就是前台進程。
2)可見進程(visible)
可見進程是一些不再前台,但用戶依然可見的進程,舉個例來說:widget、輸入法等,都屬於visible。這部分進程雖然不在前台,但與我們的使用也密切相關,我們也不希望它們被終止。
3)桌面進程(home app)
即launcher,保證在多任務切換之後,可以快速返回到home界面而不需重新載入launcher
次要服務(secondary server)
目前正在運行的一些服務(主要服務,如撥號等,是不可能被進程管理終止的,故這裡只談次要服務)
4)後台進程(hidden)
即是後台進程(background),就是我們通常意義上理解的啟動後被切換到後台的進程,如瀏覽器,閱讀器等。當程序顯示在屏幕上時,他所運行的進程即為前台進程(foreground),一旦我們按home返回主界面(注意是按home,不是按back),程序就駐留在後台,成為後台進程(background)。
5)內容供應節點(content provider)
沒有程序實體,提供內容供別的程序去用的,比如日曆供應節點,郵件供應節點等。在終止進程時,這類程序應該有較高的優先權
6)空進程(empty)
沒有任何東西在內運行的進程,有些程序,比如BTE,在程序退出後,依然會在進程中駐留一個空進程,這個進程里沒有任何數據在運行,作用往往是提高該程序下次的啟動速度或者記錄程序的一些歷史信息。這部分進程無疑是應該最先終止的。
3.進程內存空間和RAM之間的關係
進程運行的空間只是虛擬內存(邏輯內存),而程序運行時需要實實在在的內存,也就是物理內存(RAM)。在必要時,操作系統會將程序運行中申請的內存(虛擬內存)映射到RAM,讓進程能夠使用物理內存。
映射關係大致如下圖:
現在對於Android內存以及線程有了基本的了解,我們來看下系統是如何進行進程管理調度的。
4.進程管理
Android是一個多任務系統就像上文所說的系統為了優化啟動速度提升用戶體驗,當一個進程由前台進程變為後台進程時,系統不會直接將其Kill,只有剩餘內存小於應用定義的APP_MEM值,開始查看每一個進程的優先順序,然後再kill相應程序。也就是靠Low Memory Killer(幽靈劊子手)這樣一個機制來管理進程,每一個進程都有對應的adj值,系統判斷當前的剩餘內存不足時,需要進行Low Memory Killer,這時候需要判斷進程的,對每個sig->oom_adj大於min_adj的進程,找到佔用內存最大的進程存放在selected中,然後發送SIGKILL信息,殺掉該進程。
Android將程序的重要性分成以下幾類,按照重要性依次降低的順序:
5.Low Memory Killer原理
簡單來說Low Memory Killer在用戶空間中指定了一組內存臨界值,當其中的某個值與進程描述中的oom_adj值在同一範圍時,該進程將被Kill掉。
那內存臨界值和對應的oom_adj的值是在哪定義呢?這些數據在兩個文件中定義,分別是minfree和adj中定義,具體路徑是/sys/module/lowmemorykiller/parameters/,通常,在/sys/module/lowmemorykiller/parameters/adj中指定oom_adj的最小值,在/sys/module/lowmemorykiller/parameters/minfree中儲存空閑頁面的數量,但是每個手機就算是同一個產商不同型號的手機是不一樣的,因為RAM的大小可能不一樣。用NX523舉例,使用adb命令查看兩個文件
可以看到這連個文件是不可讀的,chmod命令修改為讀寫許可權,導出兩個文件
Minfree中的值為18432,23040,27648,32256,73728,137280,adj中的值為0, 58, 117, 176, 529, 1000。兩個文件對應起來看,當系統RAM剩餘空間下降到23040個頁面時,oom_adj的值大於等於58的進程將會被Kill掉,同理當RAM剩餘空間下降到27648個頁面時,oom_adj的值大於或等於117的進程將會被Kill掉。每個頁面時4KB,那麼剩餘內存值與對應的oom_adj值關係如下
72M 90M 108M 126M 288M 536M
0 58 117 176 529 1000
那如何查看終端進程對應的oom_adj值呢?進程對應的oom_adj值在系統中proc文件夾下,proc為每個進程分配了一個以進程PID命名的文件夾,文件夾下面的oom_adj的值就定義了這個進行對應的oom_adj值,所以首先需要找出進行的PID。
查看後台進程可以使用PS命令,PS命令打出來的是所有的進程非常多,可以使用管道符|進行篩選系統進程system_server,每一列值分別代表USER進程當前用戶、PID Process ID,進程ID、PPID Process Parent ID,進程的父進程ID、VSIZE Virtual Size,進程的虛擬內存大小、RSS Resident Set Size,實際駐留」在內存中」的內存大小、WCHAN休眠進程在內核中的地址、PC Program Counter、NAME進程名,詳細如下圖
可以看出system_server對應的進程號為1255,此時在proc中查看system_server對應的oom_adj
可以看到system_server的oom_adj為-16,從上面的adj文件中可以看到低於的進程是不會被LowMemoryKiller殺掉的,也就是說system_servr不會被殺掉(system_server進程掛掉會導致系統重啟)。
三、內存泄露
了解了系統的內存管理和進程調度,我們回到單個應用看下在單獨的虛擬機裡面是如何進行內存管理的,這也是學習Android內存泄露的基礎。
1.查看進程內存信息
adb shell dumpsys meminfo
(packagename也可以換為進程的PID)
2.OOM
OOM:即OutOfMemoery,顧名思義就是指內存溢出了。就像上文說的Android系統為每個應用分配了一個運行的虛擬機,在分配的同時也設置的這個虛擬機所能申請的最大內存空間,也就是一個閾值。當APP向系統申請內存的請求超過了應用所能有的最大閥值的內存,系統無法再分配多餘的空間,就會造成OOM error。這個時候就會出現報錯無響應等故障,不過比較好的是這時候系統只會將該進程殺掉,而不會影響其他進程(如果是system_process等系統進程出問題的話,則會引起系統重啟),這也是Android系統優點之一。
Android設備出廠以後,java虛擬機對單個應用的最大內存分配就確定下來了。這個屬性的定義是在/system/build.prop文件中的。在文件中定義了幾個關鍵參數:
1)Heapgrowthlimit單個應用可用最大內存主要對應的是這個值,它表示單個進程內存被限定在這個值,即程序運行過程中實際只能使用這些內存,超出就會報OOM。(僅僅針對dalvik堆,不包括native堆)
2)heapsize表示單個進程可用最大內存,但如果存在heapgrowthlimit參數,則以heapgrowthlimit為準.
3)heapstartsize堆分配的初始大小,它會影響到整個系統對RAM的使用程度,和第一次使用應用時的流暢程度。它值越小,系統ram消耗越慢,但一些較大應用一開始不夠用,需要調用gc和堆調整策略,導致應用反應較慢。它值越大,這個值越大系統ram消耗越快,但是應用更流暢。
通過上文我們知道了OOM是由於內存泄露引起的,那為什麼會發生內存泄露?
帶著這個問題我們先了解下Android系統的GC(垃圾回收)以及對象間的引用關係。
3.GC 垃圾回收
Android通過提供垃圾回收機制來管理內存,當內存不足時會觸發垃圾回收,回收沒用的對象,釋放內存。通過下面兩張圖可以很明了的看出垃圾回收機制:
這裡GC Roots表示垃圾回收器對象,每個節點表示內存中的對象,箭頭表示對象之間的引用關係,能被GC Roots直接或者間接引用到的對象ABCD,表示正在使用的對象,不能被引用到的EFG是無用對象,垃圾回收時就會被回收掉。當系統觸發一次垃圾回收時,對象EFG就會被回收。
每一次GC回收時候都會在Logcat列印一條日誌,基本格式如下:
D/dalvikvm: , ,
I/art ( 1736): Explicit concurrent mark sweep GC freed 54148(4MB) AllocSpace objects, 0(0B) LOS objects, 40% free, 20MB/34MB, paused 4.281ms total 114.734ms
第二條是NX541GC時候列印的日誌,列印的信息主要包括以下:
GC_Reason觸發這次GC操作的原因
Amount_freed系統通過這次GC操作釋放了多少內存
Heap_stats中會顯示當前內存的空閑比例以及使用情況(活動對象所佔內存/當前程序總內存)
Pause_time表示這次GC操作導致應用程序暫停的時間(在android2.3以前GC操作時會將應用程序阻塞等待GC,可能會造成卡頓,之後的版本已經優化為並行操作)
對象引用關係
a.強引用:通常我們編寫的代碼都是Strong Ref,eg:Person person = new Person("sunny");不管系統資源有多緊張,強引用的對象都絕對不會被回收,即使他以後不再用到。
b.軟引用:只要有足夠的內存,就一直保持對象。一般可用來實現緩存,通過java.lang.r.efSoftReference類實現。內存非常緊張的時候會被回收,其他時候不會被回收,所以在使用之前需要判空,從而判斷當前時候已經被回收了。
弱引用:通過WeakReference類實現,eg : WeakReference p = new WeakReference(new Person("Rain"));不管內存是否足夠,系統垃圾回收時必定會回收。
c.弱引用:通過WeakReference類實現,eg : WeakReference p = new WeakReference(new Person("Rain"));不管內存是否足夠,系統垃圾回收時必定會回收。
d.虛引用:不能單獨使用,主要是用於追蹤對象被垃圾回收的狀態。通過PhantomReference類和引用隊列ReferenceQueue類聯合使用實現。
除此之外還需要了解shallow size、retained size。簡單來說,Shallow size就是對象本身佔用內存的大小,不包含對其他對象的引用,也就是對象頭加成員變數(不是成員變數的值)的總和。Retained size是該對象自己的shallow size,加上只能從該對象能直接或間接訪問到對象的shallow size之和。換句話說,retained size是該對象被GC之後所能回收到內存的總和。
4.內存泄露Memory Leak
上述的垃圾回收只是理想狀態,但是實際在編碼過程中不可避免的會出現下面的情況
由圖中可以看出F對象已經是無用對象了,但是他一直被C對象持有,而C對象是被GC Roots持有的,這樣就會造成本該被回收的F對象通過GC(垃圾回收)之後一直無法被回收,造成了內存泄露。正如上文所說泄露的內存堆積過多無法釋放,最後內存值勢必超過分配的閾值造成OOM。
5.DDMS查看進程使用。
了解了進程內存的基本信息以及GC垃圾回收之後,看一下如何通過谷歌官方工具DDMS來查看進程內存使用情況以及GC效果。
1)將已經ROOT的手機連上DDMS,可以看到如下信息
2)選擇一個進程,點擊「Update Heap」,然後再右側點擊「Cause GC」,可以看到如下信息:
上圖中data object一行中的Total Size的值即代表進程中所有數據對象的內存總值。這時候操作手機,如果Total Size值都會穩定在一個有限的範圍內,也就是說由於程序中的的代碼良好,沒有造成對象不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對象,而在虛擬機不斷的進行GC的過程中,這些對象都被回收了,內存佔用量會會落到一個穩定的水平;反之如果代碼中存在沒有釋放對象引用的情況,則data object的Total Size值在每次GC後不會有明顯的回落,隨著操作次數的增多Total Size的值會越來越大,這時候就出現了內存泄露,如果這個值積累到超過閥值則將會出現OOM。
導出Heap文件:點擊「Dump HPROF file」,這時候DDMS會將堆棧信息導出為一個hprof文件,可以使用MAT工具進行解析。
TAG:努比亞測試技術 |