尊重原創做者,轉載請註明出處:java
http://blog.csdn.net/gemmem/article/details/8920039android
最近在網上看了很多Android內存管理方面的博文,可是文章大多都是就單個方面去介紹內存管理,沒有能全局把握,缺少系統性闡述,並且有些觀點有誤。程序員
這樣對Android內存管理進行局部性介紹,很難使讀者創建系統性概念,沒法真正理解內存管理,對提升系統優化和系統穩定性分析方面的能力是不夠的。web
我結合本身的一些思考和理解,從宏觀層面上,對內存管理作一個全局性的介紹,在此與你們交流分享。shell
首先,回顧一下基礎知識,基礎知識是理解系統機制的前提和關鍵:緩存
一、 進程的地址空間app
在32位操做系統中,進程的地址空間爲0到4GB,ide
示意圖以下:函數
圖1性能
這裏主要說明一下Stack和Heap:
Stack空間(進棧和出棧)由操做系統控制,其中主要存儲函數地址、函數參數、局部變量等等,因此Stack空間不須要很大,通常爲幾MB大小。
Heap空間的使用由程序員控制,程序員可使用malloc、new、free、delete等函數調用來操做這片地址空間。Heap爲程序完成各類複雜任務提供內存空間,因此空間比較大,通常爲幾百MB到幾GB。正是由於Heap空間由程序員管理,因此容易出現使用不當致使嚴重問題。
二、進程內存空間和RAM之間的關係
進程的內存空間只是虛擬內存(或者叫做邏輯內存),而程序的運行須要的是實實在在的內存,即物理內存(RAM)。在必要時,操做系統會將程序運行中申請的內存(虛擬內存)映射到RAM,讓進程可以使用物理內存。
RAM做爲進程運行不可或缺的資源,對系統性能和穩定性有着決定性影響。另外,RAM的一部分被操做系統留做他用,好比顯存等等,內存映射和顯存等都是由操做系統控制,咱們也沒必要過多地關注它,進程所操做的空間都是虛擬地址空間,沒法直接操做RAM。
示意圖以下:
圖2
基礎知識介紹到這裏,若是讀者理解以上知識有障礙,請好好惡補一下基礎知識,基礎理論知識相當重要。
三、 Android中的進程
(1) native進程:採用C/C++實現,不包含dalvik實例的進程,/system/bin/目錄下面的程序文件運行後都是以native進程形式存在的。如圖 3,/system/bin/surfaceflinger、/system/bin/rild、procrank等就是native進程。
(2) java進程:Android中運行於dalvik虛擬機之上的進程。dalvik虛擬機的宿主進程由fork()系統調用建立,因此每個java進程都是存在於一個native進程中,所以,java進程的內存分配比native進程複雜,由於進程中存在一個虛擬機實例。如圖3,Android系統中的應用程序基本都是java進程,如桌面、電話、聯繫人、狀態欄等等。
圖3
四、 Android中進程的堆內存
圖1和圖4分別介紹了native process和java process的結構,這個是咱們程序員須要深入理解的,進程空間中的heap空間是咱們須要重點關注的。heap空間徹底由程序員控制,咱們使用的malloc、C++ new和java new所申請的空間都是heap空間, C/C++申請的內存空間在native heap中,而java申請的內存空間則在dalvik heap中。
圖4
五、 Android的 java程序爲何容易出現OOM
這個是由於Android系統對dalvik的vm heapsize做了硬性限制,當java進程申請的java空間超過閾值時,就會拋出OOM異常(這個閾值能夠是48M、24M、16M等,視機型而定),能夠經過adb shell getprop | grep dalvik.vm.heapgrowthlimit查看此值。
也就是說,程序發生OMM並不表示RAM不足,而是由於程序申請的java heap對象超過了dalvik vm heapgrowthlimit。也就是說,在RAM充足的狀況下,也可能發生OOM。
這樣的設計彷佛有些不合理,可是Google爲何這樣作呢?這樣設計的目的是爲了讓Android系統能同時讓比較多的進程常駐內存,這樣程序啓動時就不用每次都從新加載到內存,可以給用戶更快的響應。迫使每一個應用程序使用較小的內存,移動設備很是有限的RAM就能使比較多的app常駐其中。可是有一些大型應用程序是沒法忍受vm heapgrowthlimit的限制的,後面會介紹如何讓本身的程序跳出vm heapgrowthlimit的限制。
六、 Android如何應對RAM不足
在第5點中提到:java程序發生OMM並非表示RAM不足,若是RAM真的不足,會發生什麼呢?這時Android的memory killer會起做用,當RAM所剩很少時,memory killer會殺死一些優先級比較低的進程來釋放物理內存,讓高優先級程序獲得更多的內存。咱們在分析log時,看到的進程被殺的log,如圖5,每每就是屬於這種狀況。
圖5
七、 如何查看RAM使用狀況
可使用adb shell cat /proc/meminfo查看RAM使用狀況:
MemTotal: 396708 kB
MemFree: 4088 kB
Buffers: 5212 kB
Cached: 211164 kB
SwapCached: 0 kB
Active: 165984 kB
Inactive: 193084 kB
Active(anon): 145444 kB
Inactive(anon): 248 kB
Active(file): 20540 kB
Inactive(file): 192836 kB
Unevictable: 2716 kB
Mlocked: 0 kB
HighTotal: 0 kB
HighFree: 0 kB
LowTotal: 396708 kB
LowFree: 4088 kB
SwapTotal: 0 kB
SwapFree: 0 kB
Dirty: 0 kB
Writeback: 0 kB
AnonPages: 145424 kB
……
……
這裏對其中的一些字段進行解釋:
MemTotal:可使用的RAM總和(小於實際RAM,操做系統預留了一部分)
MemFree:未使用的RAM
Cached:緩存(這個也是app能夠申請到的內存)
HightTotal:RAM中地址高於860M的物理內存總和,只能被用戶空間的程序使用。
HightFree:RAM中地址高於860M的未使用內存
LowTotal:RAM中內核和用戶空間程序均可以使用的內存總和(對於512M的RAM: lowTotal= MemTotal)
LowFree: RAM中內核和用戶空間程序未使用的內存(對於512M的RAM: lowFree = MemFree)
八、 如何查看進程的內存信息
(1)、使用adb shell dumpsys meminfo + packagename/pid:
從圖6能夠看出,com.example.demo做爲java進程有2個heap,native heap和dalvik heap,
native heap size爲159508KB,dalvik heap size爲46147KB
圖6
(2)、使用adb shell procrank查看進程內存信息
如圖7:
圖7
解釋一些字段的意思:
VSS- Virtual Set Size 虛擬耗用內存(包含共享庫佔用的內存)
RSS- Resident Set Size 實際使用物理內存(包含共享庫佔用的內存)
PSS- Proportional Set Size 實際使用的物理內存(比例分配共享庫佔用的內存)
USS- Unique Set Size 進程獨自佔用的物理內存(不包含共享庫佔用的內存)
通常來講內存佔用大小有以下規律:VSS >= RSS >= PSS >= USS
注意:dumpsys meminfo能夠查看native進程和java進程,而procrank只能查看java進程。
九、 應用程序如何繞過dalvikvm heapsize的限制
對於一些大型的應用程序(好比遊戲),內存使用會比較多,很容易超超出vm heapsize的限制,這時怎麼保證程序不會由於OOM而崩潰呢?
(1)、建立子進程
建立一個新的進程,那麼咱們就能夠把一些對象分配到新進程的heap上了,從而達到一個應用程序使用更多的內存的目的,固然,建立子進程會增長系統開銷,並且並非全部應用程序都適合這樣作,視需求而定。
建立子進程的方法:使用android:process標籤
(2)、使用jni在native heap上申請空間(推薦使用)
nativeheap的增加並不受dalvik vm heapsize的限制,從圖6能夠看出這一點,它的native heap size已經遠遠超過了dalvik heap size的限制。
只要RAM有剩餘空間,程序員能夠一直在native heap上申請空間,固然若是 RAM快耗盡,memory killer會殺進程釋放RAM。你們使用一些軟件時,有時候會閃退,就多是軟件在native層申請了比較多的內存致使的。好比,我就碰到過UC web在瀏覽內容比較多的網頁時閃退,緣由就是其native heap增加到比較大的值,佔用了大量的RAM,被memory killer殺掉了。
(3)、使用顯存(操做系統預留RAM的一部分做爲顯存)
使用OpenGL textures等API,texture memory不受dalvik vm heapsize限制,這個我沒有實踐過。再好比Android中的GraphicBufferAllocator申請的內存就是顯存。
十、Bitmap分配在native heap仍是dalvik heap上?
一種流行的觀點是這樣的:
Bitmap是jni層建立的,因此它應該是分配到native heap上,而且爲了解釋bitmap容易致使OOM,提出了這樣的觀點:
native heap size + dalvik heapsize <= dalvik vm heapsize
詳情請看:http://devspirit.blog.163.com/blog/static/16425531520104199512427/
可是請你們看看圖6,native heap size爲159508KB,遠遠超過dalvik vm heapsize,因此,事實證實以上觀點是不正確的。
正確的觀點:
你們都知道,過多地建立bitmap會致使OOM異常,且native heapsize不受dalvik限制,因此能夠得出結論:
Bitmap只能是分配在dalvik heap上的,由於只有這樣才能解釋bitmap容易致使OOM。
可是,有人可能會說,Bitmap確實是使用java native方法建立的啊,爲何會分配到dalvik heap中呢?爲了解決這個疑問,咱們仍是分析一下源碼:
涉及的文件:
framework/base/graphic/java/Android/graphics/BitmapFactory.java framework/base/core/jni/Android/graphics/BitmapFactory.cpp framework/base/core/jni/Android/graphics/Graphics.cpp
BitmapFactory.java裏面有幾個decode***方法用來建立bitmap,最終都會調用:
private staticnative Bitmap nativeDecodeStream(InputStream is, byte[] storage,Rect padding,Options opts);
而nativeDecodeStream()會調用到BitmapFactory.cpp中的deDecode方法,最終會調用到Graphics.cpp的createBitmap方法。
咱們來看看createBitmap方法的實現:
jobjectGraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, boolisMutable, jbyteArray ninepatch, int density) { SkASSERT(bitmap); SkASSERT(bitmap->pixelRef()); jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)), buffer, isMutable, ninepatch,density); hasException(env); // For the side effectof logging. return obj; }
從代碼中能夠看到bitmap對象是經過env->NewOject( )建立的,到這裏疑惑就解開了,bitmap對象是虛擬機建立的,JNIEnv的NewOject方法返回的是java對象,並非native對象,因此它會分配到dalvik heap中。
十一、java程序如何才能建立native對象
必須使用jni,並且應該用C語言的malloc或者C++的new關鍵字。實例代碼以下:
JNIEXPORT void JNICALLJava_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject) { void * p= malloc(1024*1024*50); SLOGD("allocate50M Bytes memory"); if (p !=NULL) { //memorywill not used without calling memset() memset(p,0, 1024*1024*50); } else SLOGE("mallocfailure."); …. …. free(p); //free memory }
或者:
JNIEXPORT voidJNICALL Java_com_example_demo_TestMemory_nativeMalloc(JNIEnv *, jobject) { SLOGD("allocate 50M Bytesmemory"); char *p = new char[1024 * 1024 * 50]; if (p != NULL) { //memory will not usedwithout calling memset() memset(p, 1, 1024*1024*50); } else SLOGE("newobject failure."); …. …. free(p); //free memory }
這裏對代碼中的memset作一點說明:
new或者malloc申請的內存是虛擬內存,申請以後不會當即映射到物理內存,即不會佔用RAM,只有調用memset使用內存後,虛擬內存纔會真正映射到RAM。
本文旨在讓你們對Android內存管理有一個總體性的認識,着重全局性理解,但願對你們有用。
若是對java層內存泄漏感興趣,能夠閱讀個人文章 Android內存泄漏分析及調試