1. 程式人生 > >Android記憶體管理詳細介紹 native heap dalvikheap 超有用的!!!!!!

Android記憶體管理詳細介紹 native heap dalvikheap 超有用的!!!!!!

尊重原創作者,轉載請註明出處:

最近在網上看了不少Android記憶體管理方面的博文,但是文章大多都是就單個方面去介紹記憶體管理,沒有能全域性把握,缺乏系統性闡述,而且有些觀點有誤,僅僅知道這些,還是無法從整體上理解記憶體管理,對培養系統優化和系統穩定性分析方面的能力是不夠的。

    我結合自己的一些思考和理解,從巨集觀層面上,對記憶體管理做一個全域性性的介紹,在此與大家交流分享。

首先,回顧一下基礎知識,基礎知識是理解系統機制的前提和關鍵:

1、  程序的地址空間

在32位作業系統中,程序的地址空間為0到4GB,

示意圖如下:

 

圖1

這裡主要說明一下Stack和Heap:

Stack空間(進棧和出棧)由作業系統控制,其中主要儲存函式地址、函式引數、區域性變數等等,所以Stack空間不需要很大,一般為幾MB大小。

Heap空間由程式控制,程式設計師可以使用malloc、new、free、delete等函式呼叫來操作這片地址空間。Heap為程式完成各種複雜任務提供記憶體空間,所以空間比較大,一般為幾百MB到幾GB。正是因為Heap空間由程式設計師管理,所以容易出現使用不當導致嚴重問題。

2、 程序記憶體空間和RAM之間的關係

程序的記憶體空間只是虛擬記憶體(或者叫作邏輯記憶體),而程式的執行需要的是實實在在的記憶體,即實體記憶體(RAM)。在必要時,作業系統會將程式執行中申請的記憶體(虛擬記憶體)對映到RAM,讓程序能夠使用實體記憶體。

示意圖如下:

圖2

基礎知識介紹到這裡,如果讀者理解以上知識有障礙,請好好惡補一下基礎知識,基礎理論知識至關重要。 

3、  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

4、  Android中程序的堆記憶體

RAM作為程序執行不可或缺的資源,對Android系統性能和穩定性有著決定性影響,RAM的一部分被作業系統留作他用,比如視訊記憶體等等,當然這個程式設計師無法干預,我們也不必過多地關注它。圖1和圖4分別介紹了native process和javaprocess的結構,這個是我們程式設計師需要深刻理解的,程序空間中的heap空間是我們需要重點關注的。heap空間完全由程式設計師控制,我們使用的malloc、C++ new和java new所申請的空間都是heap空間, C/C++申請的記憶體空間在native heap中,而java申請的記憶體空間則在dalvik heap中。


圖4

5、  Android的 java程式為什麼容易出現OOM

這個是因為Android系統對dalvik的vm heapsize作了硬性限制,當java程序申請的java空間超過閾值時,就會丟擲OOM異常(這個閾值可以是48M、24M、16M等,視機型而定),可以通過adb shell getprop | grep dalvik.vm.heapsize檢視此值。

也就是說,程式發生OMM並不表示RAM不足,而是因為程式申請的java heap物件超過了dalvik vm heapsize。也就是說,在RAM充足的情況下,也可能發生OOM。

這樣的設計似乎有些不合理,但是Google為什麼這樣做呢?這樣設計的目的是為了讓Android系統能同時讓比較多的程序常駐記憶體,這樣程式啟動時就不用每次都重新載入到記憶體,能夠給使用者更快的響應。迫使每個應用程式使用較小的記憶體,移動裝置非常有限的RAM就能使比較多的app常駐其中。但是有一些大型應用程式是無法忍受vm heapsize的限制的,後面會介紹如何讓自己的程式跳出vm heap size的限制。

6、  Android如何應對RAM不足

在第5點中提到:java程式發生OMM並不是表示RAM不足,如果RAM真的不足,會發生什麼呢?這時Android的memory killer會起作用,當RAM所剩不多時,memory killer會殺死一些優先順序比較低的程序來釋放實體記憶體,讓高優先順序程式得到更多的記憶體。我們在分析log時,看到的程序被殺的log,如圖5,往往就是屬於這種情況。


圖5

7、  如何檢視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)

8、  如何檢視程序的記憶體資訊

(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

注意:procrank可以檢視native程序和java程序,而dumpsys meminfo只能檢視java程序。

9、  應用程式如何繞過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 申請的記憶體就是視訊記憶體。

10、Bitmap分配在native heap還是dalvik heap上?

一種流行的觀點是這樣的:

Bitmap是jni層建立的,所以它應該是分配到native heap上,並且為了解釋bitmap容易導致OOM,提出了這樣的觀點:

              native heap size + dalvik heapsize <= dalvik vm heapsize

但是請大家看看圖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中。

11、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記憶體管理詳細介紹 native heap dalvikheap 有用

尊重原創作者,轉載請註明出處: 最近在網上看了不少Android記憶體管理方面的博文,但是文章大多都是就單個方面去介紹記憶體管理,沒有能全域性把握,缺乏系統性闡述,而且有些觀點有誤,僅僅知道這些,還是無法從整體上理解記憶體管理,對培養系統優化和系統穩定性分析方面的能力是不夠的。     我結合自己的一些思

Android 記憶體管理記錄

專案中用到大量大圖,造成快速切換Activity後記憶體不足,如登入介面用到3M高清大圖,裝置選擇用到5張大圖背景疊加效果,主介面用到了5張遮罩大圖,設定介面總的子activity中也有大圖出現。 啟動Splash——裝置選擇介面(保留棧低,不finish,但會把背景圖片回收)-->主介面-

saltstack狀態管理詳細介紹

狀態是對minion的一種描述和定義,管理人員可以不關心具體部署任務時如何完成的,只需要描述minion要達到什麼狀態,底層由salt的狀態模組來完成功能   基本入門 我們先做個小案例,使用 salt 的狀態模組安裝一個 http   1 首先修改 /etc

android 記憶體管理以及優化 粗略方案

Android的記憶體管理方式 1.android系統記憶體分配和回收方式 一個app通常就是一個程序對應一個虛擬機器 通過adb shell 檢視應用的記憶體分配情況 ①通過ps來檢視系統內的程序 ②通過 dumpsys meminfo 包名  檢視對應的應用的記憶體

Android四大元件詳細介紹及例子

這個文章主要是講Android開發的四大元件,本文主要分為 一、Activity詳解 二、Service詳解 三、Broadcast Receiver詳解 四、Content Provider詳解 外加一個重要元件 intent的詳解。 一、Activity詳解 Activty的生命週期

android開發框架總結 (二)MVP與MVVM詳細介紹與對比,如何選擇適合的框架(乾貨

前言 本篇文章將非常“細緻”地總結分析MVP與MVVM這兩種框架對於架構的選擇做了比較多的分析,應該是乾貨滿滿,如果你對這兩者的使用與選擇上還有迷惑之處。真的希望你能認真看完。 如果你是非常有經驗的程式猿,那就當相互學習總結,如果有不同看法還望指教。當然,我也是非常想進步的。  

智慧景區專案建設方案之(票務管理詳細介紹

智慧景區票務系統的解決思路 利用大資料、智慧景區和網際網路+技術和思路,解決景區智慧票務系統現有問題,建設智慧票務系統。 採用新技術架構設計:利用完善的總體架構設計,採用新的技術路線,優化完善電子票務系統。 拓展售票檢票渠道:在景區電子票務系統的基礎上擴充套件多種售票、檢票方式

Python記憶體管理演算法介紹

1      介紹 使用Python語言的一個好處是Python和其它一些高階語言一樣,會進行自動的記憶體管理。它使用引用計數機制檢測為物件分配的記憶體是否可以被釋放。然而,在Python中記憶體永遠不會還給作業系統,Python會持有這些記憶體並在需要時重新使用它們。在很

幾種常用記憶體管理底層介紹

需求 系統的實體記憶體是有限的,而對記憶體的需求是變化的, 程式的動態性越強,記憶體管理就越重要,選擇合適的記憶體管理演算法會帶來明顯的效能提升。 比如nginx, 它在每個連線accept後會malloc一塊記憶體,作為整個連線生命週期內的記憶體池。 當HTTP請求

Android 記憶體管理

Linux 程序回收 在Android中,大部分應用程式都執行在一個獨立的Linux程序中,每個程序都有獨立的記憶體空間。隨著各種應用程式啟動,系統記憶體不斷下降,為了保證新應用能夠執行,Android需要一套機制殺死暫時閒置的程序。 Android Framework並不能直接回收記憶體,其管理程序的服務

android 記憶體管理概要

一、zram zram swap 主要原理就是從記憶體分配一塊區域出來用作 swap 分割槽,每次如果記憶體空間不夠了,不是把應用程式殺掉,而是把應用程式所佔用的記憶體資料複製到 swap 分割槽,等

[譯]Android記憶體管理: 理解App的PSS

Android記憶體管理: 理解App的PSS 當在應用程式上執行Little Eye時,在記憶體檢視中,會報告有關應用程式記憶體的3個重要統計資訊。 Dalvik記憶體使用情況,即Java堆消耗的記憶體量,Native記憶體,即JVM外部程序使用的記憶體

Android記憶體管理機制詳解

無意中在MIUI看到的文章,感覺不錯,轉了過來。 原文如下: 最近看到很多機油發帖抱怨記憶體太小程序殺不掉。首先要表示,這個帖子是從百度貼吧轉來的,主要針對正常的安卓機,像里程碑這種悲劇的小記憶體機器

Android記憶體管理機制和記憶體洩漏分析及優化

Android中的記憶體管理機制 分配機制 Android為每個程序分配記憶體的時候,採用了彈性的分配方式,也就是剛開始並不會一下分配很多記憶體給每個程序,而是給每一個程序分配一個“夠用”的量。這個量是根據每一個裝置實際的實體記憶體大小來決定的。隨著應用

Linux記憶體管理詳細講解

`摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之間的關係,希

SNMP介紹及使用,有用,建議收藏

# 寫在前面 如果你是對SNMP完全不瞭解,或者只想學習如何使用現成的SNMP工具,那你找對了文章,但如果你希望學習SNMP具體協議內容,推薦閱讀官方的RFC文件。 ## 1. 簡介 SNMP(Simple Network Management Protocol) 設計在TCP/IP協議簇上的,為網路節點提供

《Linux學習並不難》用戶管理(2):/etc/passwd文件詳細介紹

Linux 用戶 passwd 9.2 《Linux學習並不難》用戶管理(2):/etc/passwd文件詳細介紹/etc/passwd文件是Linux系統識別用戶的一個重要文件,Linux系統中所有的用戶都記錄在該文件中。假設用戶以賬戶zhangsan登錄系統時,系統首先會檢查/etc/pas

《Linux學習並不難》用戶管理(3):/etc/shadow文件詳細介紹

Linux 用戶 shadow 9.3 《Linux學習並不難》用戶管理(3):/etc/shadow文件詳細介紹/etc/shadow文件是/etc/passwd的影子文件,這兩個文件應該是對應互補的。/etc/shadow文件的內容包括用戶被加密的密碼以及其它/etc/passwd文件不能包

Android劉海屏適配庫NotchFit使用詳細介紹

    NotchFit是一款Android端的劉海屏適配庫,適配了O版本和P版本,它遮蔽了不同廠商不同裝置不同系統版本對劉海屏適配帶來的一系列的繁雜的問題。      NotchFit可以智慧的判斷劉海的邏輯引數,所謂的劉海邏輯引數是該庫對裝置

Android面試系列文章2018之記憶體管理之UI卡頓篇

Android面試系列文章2018之記憶體管理之UI卡頓篇 1.UI卡頓的原理   60ftp –> 16ms: Android系統每隔16ms都會對介面進行渲染一次,造成卡頓的原因就是Android系統在渲染的時候丟幀了, 16ms = 1000/60hz,相當於60fps