Android 的內存泄露和內存限制

轉載自html

https://blog.csdn.net/goodlixueyong/article/details/40716779 java

https://blog.csdn.net/vshuang/article/details/39647167android

一、Android 進程管理&內存

Android主要應用在嵌入式設備當中,而嵌入式設備因爲一些衆所周知的條件限制,一般都不會有很高的配置,特別是內存是比較有限的。若是咱們編寫的代 碼當中有太多的對內存使用不當的地方,不免會使得咱們的設備運行緩慢,甚至是死機。爲了可以使得Android應用程序安全且快速的運行,Android 的每一個應用程序都會使用一個專有的Dalvik虛擬機實例來運行,它是由Zygote服務進程演變過來的,也就是說每一個應用程序都是在屬於本身的進程中運行的。一方面,若是程序在運行過程當中出現了內存泄漏的問題,僅僅會使得本身的進程被殺掉,而不會影響其餘進程(若是是system_process 等系統進程出問題的話,則會引發系統重啓)。另外一方面Android爲不一樣類型的進程分配了不一樣的內存使用上限,若是應用進程使用的內存超過了這個上限, 則會被系統視爲內存泄漏,從而被殺掉。

同時,Android會爲每一個應用程序分配一個單獨的LINUX用戶。Android會盡可能保留一個正在運行進程,只在內存資源出現不足時,Android會嘗試中止一些進程從而釋放足夠的資源給其餘新的進程使用, 也能保證用戶正在訪問的當前進程有足夠的資源去及時地響應用戶的事件。Android會根據進程中運行的組件類別以及組件的狀態來判斷該進程的重要性,Android會首先中止那些不重要的進程。按照重要性從高到低一共有五個級別就是咱們常說的:前臺進程、可見進程、服務進程、後臺進程、空進程。

二、單個應用可用的最大內存

Android設備出廠之後,java虛擬機對單個應用的最大內存分配就肯定下來了,超出這個值就會OOM。這個屬性值是定義在/system/build.prop文件中的
dalvik.vm.heapstartsize=8m
它表示堆分配的初始大小,它會影響到整個系統對RAM的使用程度,和第一次使用應用時的流暢程度。
它值越小,系統ram消耗越慢,但一些較大應用一開始不夠用,須要調用gc和堆調整策略,致使應用反應較慢。它值越大,這個值越大系統ram消耗越快,可是應用更流暢。

dalvik.vm.heapgrowthlimit=64m // 單個應用可用最大內存
主要對應的是這個值,它表示單個進程內存被限定在64m,即程序運行過程當中實際只能使用64m內存,超出就會報OOM。(僅僅針對dalvik堆,不包括native堆)

dalvik.vm.heapsize=384m//heapsize參數表示單個進程可用的最大內存,但若是存在heapgrowthlimit參數,則以heapgrowthlimit爲準.
heapsize表示不受控狀況下的極限堆,表示單個虛擬機或單個進程可用的最大內存。而android上的應用是帶有獨立虛擬機的,也就是每開一個應用就會打開一個獨立的虛擬機(這樣設計就會在單個程序崩潰的狀況下不會致使整個系統的崩潰)。
注意:在設置了heapgrowthlimit的狀況下,單個進程可用最大內存爲heapgrowthlimit值。在android開發中,若是要使用大堆,須要在manifest中指定android:largeHeap爲true,這樣dvm heap最大可達heapsize。

不一樣設備,這些個值能夠不同。通常地,廠家針對設備的配置狀況都會適當的修改/system/build.prop文件來調高這個值。隨着設備硬件性能的不斷提高,從最先的16M限制(G1手機)到後來的24m,32m,64m等,都遵循Android框架對每一個應用的最小內存大小限制,參考http://source.android.com/compatibility/downloads.html 3.7節。

經過代碼查看每一個進程可用的最大內存,即heapgrowthlimit值:
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int memClass = activityManager.getMemoryClass();//64,以m爲單位
 

上面的幾個參數是與虛擬機的內存分配相關的,虛擬機的內存分配過程是下面這樣的:程序員

 

1  首先判斷一下須要申請的size是否是過大,若是申請的size超過了堆的最大限制,則轉入步驟6數據庫

2  嘗試分配,若是成功則返回,失敗則轉入步驟3數組

3  判斷是否gc正在進行垃圾回收,若是正在進行則等待回收完成以後,嘗試分配。若是成功則返回,失敗則轉入步驟4緩存

4  本身啓動gc進行垃圾回收,這裏gcForMalloc的參數是false。因此不會回收軟引用,回收完成後嘗試分配,若是成功則返回,失敗則轉入步驟5安全

5  調用dvmHeapSourceAllocAndGrow嘗試分配,這個函數會擴張堆。因此heap startup的時候能夠給一個比較小的初始堆,實在不夠用再調用它進行擴張網絡

6  進入回收軟引用階段,這裏gcForMalloc的參數是ture,因此須要回收軟引用。而後調用dvmHeapSourceAllocAndGrow嘗試分配,若是失敗則拋出OOM。框架

三、爲何會內存泄露(Memory Leak)?

  android經過android虛擬機來管理內存,程序員只管申請內存建立對象,建立完再也不須要關心怎麼釋放對象內存,一切由虛擬機幫你搞定,然而虛擬機回收對象是有條件的。這裏簡單敘述下java內存管理機制,java虛擬機維護着一張當前對象關係的object tree,當GC發生時,虛擬機會從GC Roots 開始去掃描當前的對象樹,發現經過任何reference chain(引用鏈)沒法訪問某個對象的時候,該對象即被回收。名詞GC Roots正是分析這一過程的起點,例如JVM本身確保了對象的可到達性(那麼JVM就是GC Roots),因此GC Roots就是這樣在內存中保持對象可到達性的,一旦不可到達,即被回收。一般GC Roots是一個在current thread(當前線程)的call stack(調用棧)上的對象(例如方法參數和局部變量),或者是線程自身或者是system class loader(系統類加載器)加載的類以及native code(本地代碼)保留的活動對象。因此GC Roots是分析對象爲什麼還存活於內存中的利器。知道了什麼樣的對象GC纔會回收後,再來學習下對象引用都包含哪些吧。
Java中包含4種對象引用:
強引用: 一般咱們編寫的代碼都是Strong Ref,eg :Person person = new Person("sunny");無論系統資源有多緊張,強引用的對象都絕對不會被回收,即便他之後再也不用到。
軟引用:只要有足夠的內存,就一直保持對象。通常可用來實現緩存,經過java.lang.r.efSoftReference類實現。內存很是緊張的時候會被回收,其餘時候不會被回收,因此在使用以前須要判空,從而判斷當前時候已經被回收了。
弱引用:經過WeakReference類實現,eg : WeakReference p = new WeakReference(new Person("Rain"));無論內存是否足夠,系統垃圾回收時一定會回收。
虛引用:不能單獨使用,主要是用於追蹤對象被垃圾回收的狀態。經過PhantomReference類和引用隊列ReferenceQueue類聯合使用實現。
咱們可能還須要瞭解shallow size、retained size概念,簡單來講,Shallow size就是對象自己佔用內存的大小,不包含對其餘對象的引用,也就是對象頭加成員變量(不是成員變量的值)的總和。在32位系統上,對象頭佔用8字節,int佔用4字節,無論成員變量(對象或數組)是否引用了其餘對象(實例)或者賦值爲null它始終佔用4字節。故此,對於String對象實例來講,它有三個int成員(34=12字節)、一個char[]成員(14=4字節)以及一個對象頭(8字節),總共34 +14+8=24字節。根據這一原則,對String a=」rosen jiang」來講,實例a的shallow size也是24字節。Retained size是該對象本身的shallow size,加上只能從該對象能直接或間接訪問到對象的shallow size之和。換句話說,retained size是該對象被GC以後所能回收到內存的總和。爲了更好的理解retained size,咱們來看個例子。

    圖1
假設內存中對象之間的引用關係能夠當作圖1的方式,從圖中能夠看到 GC正是reference chain的起點。從obj1入手,上圖中藍色節點表明僅僅只有經過obj1才能直接或間接訪問的對象。由於能夠經過GC Roots訪問,因此左圖的obj3不是藍色節點;而在右圖倒是藍色,由於它已經被包含在retained集合內。因此對於左圖,obj1的retained size是obj一、obj二、obj4的shallow size總和;右圖的retained size是obj一、obj二、obj三、obj4的shallow size總和。
相信了有以上的這些基礎概念,咱們應該對java內存管理有了一個初步的瞭解。
 
爲何會內存泄露呢,根本緣由就是一個永遠不會被使用的對象,由於一些引用沒有斷開,沒有知足GC條件,致使不會被回收,這就形成了內存泄露。好比在Activity中註冊了一個廣播接收器,可是在頁面關閉的時候進行unRegister,就會出現內存溢出的現象。若是咱們的java運行好久,而這種內存泄露不斷的發生,最後就沒內存可用了,最終就是咱們看到的OOM錯誤。雖然android的內存泄露作到了應用程序級別的泄露(android中的每一個應用程序都是獨立運行在單獨進程中的,每一個應用進程都由虛擬機指定了一個內存上限值,一旦內存佔用值超過這個上限值,就會發生oom錯誤,進程被強制kill掉,kill掉的進程內存會被系統回收),可是對於一名開發工程師,絕對不能放過任何的內存泄露。

四、JAVA中的內存泄露

1.靜態集合類像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,全部的對象Object也不能被釋放,由於他們也將一直被Vector等應用着。

Static Vector v = new Vector(); 
for (int i = 1; i<100; i++) 
{ 
    Object o = new Object(); 
    v.add(o); 
    o = null; 
}

在這個例子中,代碼棧中存在Vector 對象的引用 v 和 Object 對象的引用 o 。在 For 循環中,咱們不斷的生成新的對象,而後將其添加到 Vector 對象中,以後將 o 引用置空。問題是當 o 引用被置空後,若是發生 GC,咱們建立的 Object 對象是否可以被 GC 回收呢?答案是否認的。由於, GC 在跟蹤代碼棧中的引用時,會發現 v 引用,而繼續往下跟蹤,就會發現 v 引用指向的內存空間中又存在指向 Object 對象的引用。也就是說盡管o 引用已經被置空,可是 Object 對象仍然存在其餘的引用,是能夠被訪問到的,因此 GC 沒法將其釋放掉。若是在此循環以後, Object 對象對程序已經沒有任何做用,那麼咱們就認爲此 Java 程序發生了內存泄漏。

2.各類鏈接,數據庫鏈接,網絡鏈接,IO鏈接等沒有顯示調用close關閉,不被GC回收致使內存泄露。

3.監聽器的使用,在釋放對象的同時沒有相應刪除監聽器的時候也可能致使內存泄露。

五、爲何會發生OOM(Out Of Memory)?

OOM:即OutOfMemoery,顧名思義就是指內存溢出了。以前咱們知道Android的應用程序所能申請的最大內存都是有限的,OOM是指APP向系統申請內存的請求超過了應用所能有的最大閥值的內存,系統沒法再分配多餘的空間,就會形成OOM error。在Android平臺下,除了以前所說的持續發生了內存泄漏(Memory Leak),累積到必定程度致使OOM的狀況之外,也有一次性申請不少內存,好比說一次建立大的數組或者是載入大的文件如圖片的時候。實際中不少狀況就是出如今圖片不當處理加載的時候。

六、常見的MemoryLeak分析 

後來看到了更多的MemoryLeak相關的知識,有了更多的實踐經驗,
就此小小總結了一下,見  Android 內存優化 (防Memory Leak)
相關文章
相關標籤/搜索