Android 內(nèi)存管理

  1. 概述
  2. 虛擬內(nèi)存
    2.1 分頁
    2.2 內(nèi)存映射
  3. 內(nèi)存不足時(shí)的處理
    3.1 kswapd
    3.2 LMK
  4. 虛擬機(jī)
    4.1 堆空間劃分
    4.2 回收算法

在看這篇文章之前,需要Linux內(nèi)存管理基礎(chǔ),推薦Linux 內(nèi)存管理

對于這篇文章的結(jié)構(gòu)我也是思慮再三传轰,為什么先講LMK,后講虛擬機(jī)回收措译,主要是因?yàn)槁鸷疲猪摽场?nèi)存映射猖败、LMK是直接影響物理內(nèi)存的速缆,而虛擬機(jī)對應(yīng)的更多的是硬件無關(guān)的。此外辙浑,本篇不涉及具體代碼實(shí)現(xiàn)激涤,詳細(xì)實(shí)現(xiàn)以后再補(bǔ)拟糕。

概述

Android 使用的是Linux內(nèi)核判呕,但是這個(gè)Linux內(nèi)核是根據(jù)Android所需,在文件系統(tǒng)送滞、內(nèi)存管理侠草、進(jìn)程間通信機(jī)制和電源管理方面等方面進(jìn)行了修改了的,繼承了Linux內(nèi)核的諸多優(yōu)點(diǎn)犁嗅,保留了Linux內(nèi)核的主題框架边涕,同時(shí)能夠更好的工作在移動(dòng)設(shè)備上。

Android同樣使用分頁內(nèi)存映射來構(gòu)建虛擬內(nèi)存褂微,同時(shí)使用垃圾回收器回收內(nèi)存功蜓,使用LowMemoryKiller(LMK)在低內(nèi)存的時(shí)候來殺死進(jìn)程釋放更多內(nèi)存。

值得注意的是宠蚂,應(yīng)用修改的任何內(nèi)存式撼,無論修改的方式是分配新對象還是輕觸內(nèi)存映射的頁面,都會(huì)一直駐留在 RAM 中求厕,并且不會(huì)換出到磁盤著隆。

每一個(gè)應(yīng)用都是一個(gè)獨(dú)立的虛擬機(jī),以一個(gè)Linux進(jìn)程的形式存在呀癣,在Android上美浦,4.4之前都是Dalvik虛擬機(jī),5.0之后默認(rèn)都是ART虛擬機(jī)了项栏,對于垃圾回收機(jī)制浦辨,Android ART虛擬機(jī)默認(rèn)使用CMS來清理回收對象。

對于回收進(jìn)程資源這回事沼沈,Linux在進(jìn)程退出的時(shí)候荤牍,就會(huì)釋放當(dāng)前Linux的內(nèi)存案腺,但是Android為了提高的切換進(jìn)程時(shí)的啟動(dòng)速度,會(huì)將這些進(jìn)程都保存在內(nèi)存中康吵,知道系統(tǒng)需要更多的內(nèi)存為止劈榨。LMK每個(gè)一段時(shí)間都會(huì)檢查一次,當(dāng)內(nèi)存值較低的時(shí)候晦嵌,LMK就會(huì)根據(jù)不同的剩余內(nèi)存檔位來來選擇殺不同優(yōu)先級的進(jìn)程同辣。其實(shí)在Linux也有類似的管理策略,即OOM killer惭载,全稱(Out Of Memory Killer), OOM的策略更多的是用于分配內(nèi)存不足時(shí)觸發(fā)旱函,得分最高的進(jìn)程殺掉。當(dāng)需要OOM killer處理的時(shí)候描滔,系統(tǒng)可能已經(jīng)處于異常狀態(tài)棒妨,而Android更像是在未雨綢繆。

虛擬內(nèi)存

分頁

分頁的Linux很多具體實(shí)現(xiàn)我們已經(jīng)聊過含长,既然Android是從Linux開始改的券腔,那么很多其實(shí)都是一樣的,這里著重介紹一下他不同的地方拘泞。

首先纷纫,Android的內(nèi)存形式被分為三種:RAM、zRAM 和存儲(chǔ)器陪腌。

  • RAM 就是我們常說的物理內(nèi)存辱魁,是最快的內(nèi)存類型,但其大小通常有限诗鸭。
  • zRAM 是從RAM開辟出來的一塊區(qū)域染簇,是用于交換空間的 RAM 分區(qū)。所有數(shù)據(jù)在放入 zRAM 時(shí)都會(huì)進(jìn)行壓縮强岸,然后在從 zRAM 向外復(fù)制時(shí)進(jìn)行解壓縮锻弓。這部分 RAM 會(huì)隨著頁面進(jìn)出 zRAM 而增大或縮小。設(shè)備制造商可以設(shè)置 zRAM 大小上限请唱。
  • 存儲(chǔ)器弥咪,這一部分就是我們常說的磁盤,存儲(chǔ)器中包含所有持久性數(shù)據(jù)(例如文件系統(tǒng)等)十绑,以及為所有應(yīng)用聚至、庫和平臺(tái)添加的對象代碼,存儲(chǔ)器比另外兩種內(nèi)存的容量大得多本橙。

在 Android 上扳躬,存儲(chǔ)器不像在其他 Linux 實(shí)現(xiàn)上那樣用于交換空間,因?yàn)轭l繁寫入會(huì)導(dǎo)致這種內(nèi)存出現(xiàn)損壞,并縮短存儲(chǔ)媒介的使用壽命贷币,而是所有內(nèi)存都是一直駐留RAM中击胜。只是在實(shí)現(xiàn)分頁的時(shí)候,需要一塊輔村來做擔(dān)保役纹,Linux選擇了磁盤偶摔,Android選擇了壓縮+RAM。這部分跟Linux大同小異促脉,所以就不展開敘述辰斋,Linux參考這里.

對于RAM與zRAM,Android 使用了跟 Linux 相似的管理手法瘸味,分頁宫仗。RAM 分為多個(gè)“頁”。通常旁仿,每個(gè)頁面為 4KB 的內(nèi)存藕夫。系統(tǒng)會(huì)將頁面分為“可用”或“已使用”。對于已使用的內(nèi)存可以分為以下類別:

  • 緩存頁:在儲(chǔ)存器中有對應(yīng)文件的內(nèi)存枯冈,例如毅贮,代碼或內(nèi)存映射文件。緩存頁也分為兩種:
    • 干凈頁(clean):存儲(chǔ)器中未經(jīng)修改的文件副本霜幼,可由 kswapd刪除以增加可用內(nèi)存
    • 臟頁(dirty):存儲(chǔ)器中已經(jīng)被修改的文件副本嫩码,可由 kswapd 移動(dòng)到 zRAM 中進(jìn)行壓縮儲(chǔ)存
  • 共享頁:由多個(gè)進(jìn)程共享使用的頁面
    • 干凈頁:存儲(chǔ)器中未經(jīng)修改的文件副本誉尖,可由 kswapd 刪除以增加可用內(nèi)存
    • 臟頁:存儲(chǔ)器中已經(jīng)被修改的文件副本罪既;允許通過 kswapd 或者通過明確使用 msync() 或 munmap() 將更改寫回存儲(chǔ)器中的文件,然后就變成干凈頁铡恕,可刪除
  • 匿名頁:存儲(chǔ)器中沒有對應(yīng)文件的內(nèi)存(例如琢感,由設(shè)置了 MAP_ANONYMOUS 標(biāo)記的 mmap() 進(jìn)行分配)
    • 臟頁:可由 kswapd 移動(dòng)到 zRAM/在 zRAM 中進(jìn)行壓縮以增加可用內(nèi)存

匿名頁中,由于不可以回寫到存儲(chǔ)器探熔,無論是在RAM或者zRAM中驹针,一直都在內(nèi)存中,所以只能是臟頁诀艰。同時(shí)柬甥,干凈頁可以刪除,因?yàn)槭冀K可以使用存儲(chǔ)器中的數(shù)據(jù)重新生成它們其垄。

內(nèi)存映射

內(nèi)存映射(mmap)是一種內(nèi)存映射文件的方法苛蒲,即將一個(gè)文件或者其他對象映射到進(jìn)程的地址空間,實(shí)現(xiàn)文件磁盤地址和應(yīng)用程序進(jìn)程虛擬地址空間中一段虛擬地址的一一映射關(guān)系绿满。實(shí)現(xiàn)這樣的映射關(guān)系后臂外,進(jìn)程就可以采用指針的方式讀寫操作這一段內(nèi)存,而系統(tǒng)會(huì)自動(dòng)回寫臟頁面到對應(yīng)的文件磁盤上。應(yīng)用程序處理映射部分如同訪問主存漏健。

分頁和內(nèi)存映射的區(qū)別:

在于分頁文件操作在進(jìn)程訪存時(shí)是需要先查詢頁面緩存(page cache)的嚎货,若發(fā)生缺頁中斷,需要通過inode定位文件磁盤地址蔫浆,先把缺失文件復(fù)制到page cache殖属,再從page cache復(fù)制到內(nèi)存中,才能進(jìn)行訪問瓦盛。這樣訪存需要經(jīng)過兩次文件復(fù)制忱辅,寫操作也是一樣√犯龋總結(jié)來說墙懂,常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤,使用了頁緩存機(jī)制扮念。這樣造成讀文件時(shí)需要先將文件頁從磁盤拷貝到頁緩存中损搬,由于頁緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址柜与,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內(nèi)存對應(yīng)的用戶空間中巧勤。但mmap的優(yōu)勢在于,把磁盤文件與進(jìn)程虛擬地址做了映射弄匕,這樣可以跳過page cache颅悉,只使用一次數(shù)據(jù)拷貝

內(nèi)存不足時(shí)的處理

Android 有兩種處理內(nèi)存不足的進(jìn)程:內(nèi)核交換守護(hù)進(jìn)程(kswapd)和低內(nèi)存終止守護(hù)進(jìn)程(LowMemoryKiller)迁匠。對應(yīng)著兩種內(nèi)存不足時(shí)候的處理方式剩瓶。

kswapd

kswapd, 是 Linux 內(nèi)核的一部分城丧,用于將已使用內(nèi)存轉(zhuǎn)換為可用內(nèi)存延曙。當(dāng)設(shè)備上的可用內(nèi)存不足時(shí),該守護(hù)進(jìn)程將變?yōu)榛顒?dòng)狀態(tài)亡哄。Linux 內(nèi)核設(shè)有可用內(nèi)存上下限閾值枝缔。當(dāng)可用內(nèi)存降至下限閾值以下時(shí),kswapd 開始回收內(nèi)存頁蚊惯。當(dāng)可用內(nèi)存達(dá)到上限閾值時(shí)愿卸,kswapd 停止回收內(nèi)存頁。

kswapd 可以對干凈頁進(jìn)行刪除截型,當(dāng)進(jìn)程需要該頁面的時(shí)候趴荸,只需要從存儲(chǔ)器中讀回。同時(shí)也可以將臟頁移入zRAM中壓縮存儲(chǔ)菠劝,壓縮率越高赊舶,相對釋放的內(nèi)存也就越多睁搭。如果進(jìn)程需要該頁,解壓調(diào)出即可笼平,跟Linux中的交換分區(qū)處理邏輯相同园骆。

LMK

系統(tǒng)會(huì)定時(shí)檢查內(nèi)存可用值,當(dāng)?shù)陀谔囟ǖ拈撝翟⒌鳎琇MK就會(huì)開始?xì)⑦M(jìn)程锌唾,直到可用內(nèi)存值高于閾值才會(huì)停止殺內(nèi)存。在介紹LMK之前夺英,我們需要了解LMK的三個(gè)關(guān)鍵參數(shù):

  • oom_adj:在 Framework 層使用晌涕,代表進(jìn)程的優(yōu)先級,數(shù)值越高痛悯,優(yōu)先級越低余黎,越容易被殺死。
  • oom_adj threshold:在 Framework 層使用载萌,代表 oom_adj 的內(nèi)存閾值惧财。Android Kernel 會(huì)定時(shí)檢測當(dāng)前剩余內(nèi)存是否低于這個(gè)閥值,若低于這個(gè)閾值扭仁,則會(huì)根據(jù) oom_score_adj 參數(shù)的值殺進(jìn)程垮衷,數(shù)值越大越先殺。
  • oom_score_adj: 在 Kernel 層使用乖坠,由 oom_adj 換算而來搀突,是殺死進(jìn)程時(shí)實(shí)際使用的參數(shù)。

在Android 6.0(API23)及之前版本 oom_adj 的取值范圍為 [-17, 16]熊泵,在這里給出 oom_adj 和 oom_score_adj 轉(zhuǎn)換關(guān)系:

  • 當(dāng)oom_adj = 15, 則oom_score_adj=1000;
  • 當(dāng)oom_adj < 15, 則oom_score_adj= oom_adj * 1000/17;

在簡述LMK流程之前仰迁,需要對于這三個(gè)參數(shù)有一定的認(rèn)識哦。下面就是 adj 值的列表戈次,值越大越容易被殺:

常量定義 常量取值 含義
NATIVE_ADJ -1000 native進(jìn)程(不被系統(tǒng)管理)
SYSTEM_ADJ -900 系統(tǒng)進(jìn)程
PERSISTENT_PROC_ADJ -800 系統(tǒng)persistent進(jìn)程轩勘,比如telephony
PERSISTENT_SERVICE_ADJ -700 關(guān)聯(lián)著系統(tǒng)或persistent進(jìn)程
FOREGROUND_APP_ADJ 0 前臺(tái)進(jìn)程
VISIBLE_APP_ADJ 100 可見進(jìn)程
PERCEPTIBLE_APP_ADJ 200 可感知進(jìn)程筒扒,比如后臺(tái)音樂播放
BACKUP_APP_ADJ 300 備份進(jìn)程
HEAVY_WEIGHT_APP_ADJ 400 后臺(tái)的重量級進(jìn)程怯邪,system/rootdir/init.rc文件中設(shè)置
SERVICE_ADJ 500 服務(wù)進(jìn)程
HOME_APP_ADJ 600 Home進(jìn)程
PREVIOUS_APP_ADJ 700 上一個(gè)App的進(jìn)程
SERVICE_B_ADJ 800 B List中的Service(較老的、使用可能性更谢ǘ铡)
CACHED_APP_MIN_ADJ 900 不可見進(jìn)程的adj最小值
CACHED_APP_MAX_ADJ 906 不可見進(jìn)程的adj最大值
UNKNOWN_ADJ 1001 一般指將要會(huì)緩存進(jìn)程悬秉,無法獲取確定值

對此,官網(wǎng)也給出了一個(gè)殺進(jìn)程的順序圖:


lmkd 是由 init進(jìn)程通過解析 init.rc 文件來啟動(dòng)的守護(hù)進(jìn)程冰蘑,可監(jiān)控運(yùn)行中的 Android 系統(tǒng)的內(nèi)存狀態(tài)和泌,并通過終止最不必要的進(jìn)程來解決內(nèi)存壓力,使系統(tǒng)以可接受的水平運(yùn)行祠肥。lmkd 需要跟 framework 層交互武氓,因?yàn)閒ramework層知道各個(gè)進(jìn)程當(dāng)前的狀態(tài)(比如是否在前臺(tái)等),lmkd 進(jìn)程會(huì)創(chuàng)建名為 lmkd 的 socket,節(jié)點(diǎn)位于 /dev/socket/lmkd县恕,用于與framework交互东羹。

lmkd 啟動(dòng)后,便會(huì)進(jìn)入循環(huán)等待狀態(tài)忠烛,接受來自 ProcessList 的三個(gè)命令:

命令 功能 方法
LMK_TARGET 初始化 oom_adj ProcessList::setOomAdj()
LMK_PROCPRIO 更新 oom_adj ProcessList::updateOomLevels()
LMK_PROCREMOVE 移除進(jìn)程(暫時(shí)無用) ProcessList::remove()

Android 四大組件直接影響了oom_adj值属提,ActivityManagerService 會(huì)根據(jù)當(dāng)前應(yīng)用進(jìn)程托管組件(即四大組件)生命周期的變化,及時(shí)的調(diào)用 applyOomAdjLocked()美尸,更新進(jìn)程狀態(tài)及該狀態(tài)對應(yīng)的 oom_adj冤议。

虛擬機(jī)

為什么需要 Java 虛擬機(jī)?為什么 Java 說 write once, run everywhere 师坎?

在各個(gè)平臺(tái)都有對應(yīng)的虛擬機(jī)恕酸,他們負(fù)責(zé)將符合JVM對加載編譯文件格式要求的語言進(jìn)行解釋執(zhí)行。JVM屏蔽了與具體操作系統(tǒng)平臺(tái)相關(guān)的信息胯陋,使得Java程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼字節(jié)碼尸疆,就可以在多種平臺(tái)上不加修改地運(yùn)行。這也意味惶岭,JVM 有自己完善的硬體架構(gòu)寿弱,如處理器堆棧按灶、寄存器等症革,還具有相應(yīng)的指令系統(tǒng),與具體的操作系統(tǒng)無關(guān)鸯旁。

而在 Android 上噪矛,同JVM一樣,Dalvik 或者 ART 負(fù)責(zé)將字節(jié)碼解釋成 Android 可以執(zhí)行的指令铺罢,而每一個(gè)Dalvik 或者 ART 提供了對象生命周期管理艇挨、堆棧管理、線程管理韭赘、安全和異常管理以及垃圾回收等重要功能缩滨,各自擁有一套完整的指令系統(tǒng)。但是泉瞻,JVM其核心目的脉漏,是為了構(gòu)建一個(gè)真正跨OS平臺(tái),跨指令集的程序運(yùn)行環(huán)境(VM)袖牙,DVM的目的更像是為了將android OS的本地資源和環(huán)境侧巨,以一種統(tǒng)一的界面提供給應(yīng)用程序開發(fā)

堆空間劃分

網(wǎng)上大部分都是堆Dalvik和ART主要講解的都是堆內(nèi)存及回收鞭达,想來其他區(qū)域的原理與Java虛擬機(jī)規(guī)范原理差不多司忱。話不多說皇忿,我們先看 Dalvik 和 ART 虛擬機(jī)對運(yùn)行時(shí)堆的空間劃分:

Dalvik 堆

在 Dalvik 中,默認(rèn)使用 標(biāo)記-清除(Mark-Sweep)算法 來進(jìn)行垃圾回收坦仍,在運(yùn)行時(shí)禁添,堆被分成2個(gè)Space和多個(gè)輔助數(shù)據(jù)結(jié)構(gòu)。

  • Zygote Space
    主要是用來管理Zygote進(jìn)程在啟動(dòng)過程中預(yù)加載和創(chuàng)建的各種對象桨踪,在Zygote Space中不會(huì)出發(fā)GC老翘,同時(shí)在Zygote進(jìn)程和應(yīng)用程序之間共享Zygote Space。

  • Allocation Space
    在Zygote Space進(jìn)程fork第一個(gè)子進(jìn)程之前锻离,會(huì)把Zygote Space分為2個(gè)部分铺峭,原來的已經(jīng)被使用的那部分堆仍舊是Zygote Space,而未使用的那部分堆就叫做Allocation Space汽纠,以后的對象都會(huì)在Allocation Space上進(jìn)行分配和釋放卫键。Allocation Space不是進(jìn)程間共享的,每個(gè)進(jìn)程中都獨(dú)立擁有一份虱朵。

DVM中數(shù)據(jù)結(jié)構(gòu)如下:

  • Card Table
    用于DVM的并發(fā) GC莉炉,當(dāng)?shù)谝淮芜M(jìn)行垃圾標(biāo)記后,記錄垃圾信息碴犬。

  • Heap Bitmap
    有2個(gè)堆位圖絮宁,一個(gè)用來記錄上次GC存活的對象,另一個(gè)用來記錄這次GC存活的對象服协。

  • Mark Stack
    DVM的運(yùn)行時(shí)進(jìn)行GC時(shí)用來進(jìn)行標(biāo)記的數(shù)據(jù)結(jié)構(gòu)

Android系統(tǒng)的第一個(gè)Dalvik虛擬機(jī)是由Zygote進(jìn)程創(chuàng)建的绍昂,而其他的應(yīng)用進(jìn)程是由 Zygote 進(jìn)程 fork 出來的,為了盡量地避免拷貝偿荷,應(yīng)用程序進(jìn)程使用了一種寫時(shí)拷貝技術(shù)(COW)來復(fù)制了Zygote進(jìn)程的地址空間窘游。

Zygote進(jìn)程在啟動(dòng)過程中創(chuàng)建Dalvik虛擬機(jī)的時(shí)候,其實(shí)只有一個(gè)堆跳纳,但是當(dāng)Zygote進(jìn)程在fork第一個(gè)應(yīng)用程序進(jìn)程之前忍饰,會(huì)將已經(jīng)使用了的那部分堆內(nèi)存劃分為一部分,還沒有使用的堆內(nèi)存劃分為另外一部分寺庄,前面那部分就是 Zygote Space艾蓝。

當(dāng)應(yīng)用程序或者Zygote 進(jìn)程想要new一個(gè)新的對象的時(shí)候就會(huì)在 Allocation Space 上分配。每一個(gè)應(yīng)用進(jìn)程都會(huì)共享一個(gè) Zygote Space铣揉,當(dāng)Zygote進(jìn)程或者應(yīng)用程序進(jìn)程對該堆進(jìn)行寫操作時(shí)饶深,內(nèi)核就會(huì)執(zhí)行真正的拷貝一份提供給進(jìn)程進(jìn)行寫操作。

ART 堆

ART運(yùn)行時(shí)堆劃分為四個(gè)空間逛拱,分別是Image Space、Zygote Space台猴、Allocation Space和Large Object Space朽合。其中前三個(gè)Space是連續(xù)的(Continuous Space)俱两,Large Object Space是一些離散地址的集合(Discontinuous Space),用于分配一些大對象曹步。

在Image Space和Zygote Space之間宪彩,隔著一段用來映射system@framework@boot.art@classes.oat 文件的內(nèi)存,system@framework@boot.art@classes.oat 是一個(gè)OAT文件讲婚,它是由在系統(tǒng)啟動(dòng)類路徑中的所有DEX文件翻譯得到的尿孔。

其中,Zygote Space筹麸、Allocation Space 跟 Dalvik 中的空間生成方式和含義相同活合。

  • Image Space
    包含了那些需要預(yù)加載的系統(tǒng)類對象。這意味著需要預(yù)加載的類對象是在生成system@framework@boot.art@classes.oat這個(gè)OAT文件的時(shí)候創(chuàng)建并且保存在文件system@framework@boot.art@classes.dex中物赶,以后只要系統(tǒng)啟動(dòng)類路徑中的DEX文件不發(fā)生變化(即不發(fā)生更新升級)白指,那么以后每次系統(tǒng)啟動(dòng)只需要將文件system@framework@boot.art@classes.dex直接映射到內(nèi)存即可,省去了創(chuàng)建各個(gè)類對象的時(shí)間酵紫。之前使用Dalvik虛擬機(jī)作為應(yīng)用程序運(yùn)行時(shí)時(shí)告嘲,每次系統(tǒng)啟動(dòng)時(shí),都需要為那些預(yù)加載的類創(chuàng)建類對象奖地。因此橄唬,雖然ART運(yùn)行時(shí)第一次啟動(dòng)時(shí)會(huì)比較慢,但是以后啟動(dòng)實(shí)際上會(huì)更快参歹。

  • Large Object Space

    • 其中一種實(shí)現(xiàn)和Continuous Space的實(shí)現(xiàn)類似轧坎,預(yù)先分配好一塊大的內(nèi)存空間,然后再在上面為對象分配內(nèi)存塊泽示。不過這種方式實(shí)現(xiàn)的Large Object Space不像Continuous Space通過C庫的內(nèi)塊管理接口來分配和釋放內(nèi)存缸血,而是自己維護(hù)一個(gè)Free List。每次為對象分配內(nèi)存時(shí)械筛,都是從這個(gè)Free List找到合適的空閑的內(nèi)存塊來分配众弓。釋放內(nèi)存的時(shí)候狈醉,也是將要釋放的內(nèi)存添加到該Free List去。

    • 另外一種Large Object Space實(shí)現(xiàn)是每次為對象分配內(nèi)存時(shí),都單獨(dú)為其映射一新的內(nèi)存盼产。也就是說,為每一個(gè)對象分配的內(nèi)存塊都是相互獨(dú)立的牡属。這種實(shí)現(xiàn)方式相比上面介紹的Free List實(shí)現(xiàn)方式渊涝,也更簡單一些。在Android 4.4中抛计,ART運(yùn)行時(shí)使用的是后一種實(shí)現(xiàn)方式哄孤。

對應(yīng)的數(shù)據(jù)結(jié)構(gòu):

  • Mod Union Table
    Mod Union Table 是與Card Table配合使用的,用來記錄在一次GC過程中吹截,記錄不會(huì)被回收的Space的對象對會(huì)被回收的Space的引用瘦陈。例如凝危,Image Space的對象對Zygote Space和Allocation Space的對象的引用,以及Zygote Space的對象對Allocation Space的對象的引用晨逝。

回收算法

Dalvik

Dalvik 虛擬機(jī)默認(rèn)采用 Mark-Sweep 算法蛾默,不會(huì)進(jìn)行整理,所以長時(shí)間運(yùn)行會(huì)產(chǎn)生碎片問題捉貌。但是值得注意的是支鸡,在某些情況下,Dalvik 也會(huì)采用并發(fā)Gc趁窃。


Mark-Sweep 算法步驟

(a)GC 前的狀態(tài)牧挣。示例中有一個(gè) GC Root,所有對象都未被標(biāo)記棚菊。
(b)GC 標(biāo)記后的狀態(tài)浸踩。在標(biāo)記階段,所有活動(dòng)對象(Active Objects)都會(huì)被標(biāo)記统求。
(c)GC 清除后的狀態(tài)检碗。所有垃圾已被回收,并且所有活動(dòng)對象的標(biāo)記狀態(tài)都被重置為 false码邻。

垃圾回收原因可以在Dalvik 日志消息中看到:

  • GC_CONCURRENT
    在您的堆開始占用內(nèi)存時(shí)可以釋放內(nèi)存的并發(fā)垃圾回收折剃。
  • GC_FOR_MALLOC
    堆已滿而系統(tǒng)不得不停止您的應(yīng)用并回收內(nèi)存時(shí),您的應(yīng)用嘗試分配內(nèi)存而引起的垃圾回收像屋。
  • GC_HPROF_DUMP_HEAP
    當(dāng)您請求創(chuàng)建 HPROF 文件來分析堆時(shí)出現(xiàn)的垃圾回收怕犁。
  • GC_EXPLICIT
    顯式垃圾回收,例如當(dāng)您調(diào)用 gc() 時(shí)(您應(yīng)避免調(diào)用己莺,而應(yīng)信任垃圾回收會(huì)根據(jù)需要運(yùn)行
  • GC_EXTERNAL_ALLOC
    這僅適用于 API 級別 10 及更低級別(更新版本會(huì)在 Dalvik 堆中分配任何內(nèi)存)奏甫。外部分配內(nèi)存的垃圾回收(例如存儲(chǔ)在原生內(nèi)存或 NIO 字節(jié)緩沖區(qū)中的像素?cái)?shù)據(jù))。

ART

ART 具有可以運(yùn)行的多種不同的垃圾回收凌受,整個(gè)堆回收器會(huì)釋放和回收 Image Space 以外的所有其他空間阵子。Art 在GC上不像Dalvik僅有一種回收算法,Art在不同的情況下會(huì)選擇不同的回收算法胜蛉,比如Alloc內(nèi)存不夠的時(shí)候會(huì)采用非并發(fā)GC挠进,而在Alloc后發(fā)現(xiàn)內(nèi)存達(dá)到一定閥值的時(shí)候又會(huì)觸發(fā)并發(fā)GC。

Dalvik 和 ART gc 流程對比

Dalvik 垃圾回收 (GC) 可能有損于應(yīng)用性能誊册,從而導(dǎo)致顯示不穩(wěn)定领突、界面響應(yīng)速度緩慢以及其他問題。ART 通過以下幾種方式對垃圾回收做了優(yōu)化:

  • 只有一次(而非兩次)GC 暫停
  • 在 GC 保持暫停狀態(tài)期間并行處理
  • 在清理最近分配的短時(shí)對象這種特殊情況中案怯,回收器的總 GC 時(shí)間更短
  • 優(yōu)化了垃圾回收的工效君旦,能夠更加及時(shí)地進(jìn)行并行垃圾回收,這使得 GC_FOR_ALLOC 事件在典型用例中極為罕見
  • 壓縮 GC 以減少后臺(tái)內(nèi)存使用和碎片

從谷歌提供的數(shù)據(jù)來看于宙,Art相對Dalvik內(nèi)存分配的效率提高了10倍浮驳,GC的效率提高了2-3倍悍汛。

GC 類型:

  • Concurrent
    不會(huì)暫停應(yīng)用線程的并發(fā)垃圾回收捞魁。此垃圾回收在后臺(tái)線程中運(yùn)行,而且不會(huì)阻止分配离咐。
  • Alloc
    您的應(yīng)用在堆已滿時(shí)嘗試分配內(nèi)存引起的垃圾回收谱俭。在這種情況下,分配線程中發(fā)生了垃圾回收宵蛀±ブ可能造成卡頓。
  • Explicit
    由應(yīng)用明確請求的垃圾回收术陶,例如凑懂,通過調(diào)用 System#gc()Runtime#gc()。與 Dalvik 相同梧宫。不建議使用顯式垃圾回收接谨,因?yàn)樗鼈儠?huì)阻止分配線程并不必要地浪費(fèi) CPU 周期。如果顯式垃圾回收導(dǎo)致其他線程被搶占塘匣,那么它們也可能會(huì)導(dǎo)致卡頓(應(yīng)用中出現(xiàn)間斷脓豪、抖動(dòng)或暫停)。
  • NativeAlloc
    Native 分配(如位圖或 RenderScript 分配對象)導(dǎo)致出現(xiàn)原生內(nèi)存壓力忌卤,進(jìn)而引起的回收扫夜。
  • CollectorTransition
    由堆轉(zhuǎn)換引起的回收;此回收由運(yùn)行時(shí)切換垃圾回收引起驰徊◇源常回收器轉(zhuǎn)換包括將所有對象從空閑列表空間復(fù)制到碰撞指針空間(反之亦然)。當(dāng)前棍厂,回收器轉(zhuǎn)換僅在以下情況下出現(xiàn):在 RAM 較小的設(shè)備上颗味,應(yīng)用將進(jìn)程狀態(tài)從可察覺的暫停狀態(tài)變更為可察覺的非暫停狀態(tài)(反之亦然)。
  • HomogeneousSpaceCompact
    齊性空間壓縮是空閑列表空間到空閑列表空間壓縮勋桶,通常在應(yīng)用進(jìn)入到可察覺的暫停進(jìn)程狀態(tài)時(shí)發(fā)生脱衙。這樣做的主要原因是減少 RAM 使用量并對堆進(jìn)行碎片整理。
  • DisableMovingGc
    這不是真正的垃圾回收原因例驹,但請注意捐韩,發(fā)生并發(fā)堆壓縮時(shí),由于使用了 GetPrimitiveArrayCritical鹃锈,回收遭到阻止荤胁。一般情況下,強(qiáng)烈建議不要使用 GetPrimitiveArrayCritical屎债,因?yàn)樗谝苿?dòng)回收器方面具有限制仅政。
  • HeapTrim
    這不是垃圾回收原因垢油,但請注意,堆修剪完成之前回收會(huì)一直受到阻止圆丹。

參考文章:
官方文檔-內(nèi)存管理概覽
官方文檔-管理應(yīng)用內(nèi)存
官方文檔-進(jìn)程間的內(nèi)存分配
官方文檔-平臺(tái)架構(gòu)
官方文檔-調(diào)查 RAM 使用情況
官方文檔-Android Runtime (ART) 和 Dalvik
淺談內(nèi)存映射
android底層之什么是Zram滩愁?
官方文檔-Android Runtime (ART) 和 Dalvik
分頁-維基百科
內(nèi)存映射文件-維基百科
Java中什么是JVM及其工作原理?
Android和Linux的關(guān)系
Android LowMemoryKiller原理分析
Android LowMemoryKiller 簡介
Android內(nèi)存管理之LMK和OOM
Java虛擬機(jī)
Android內(nèi)存管理分析總結(jié)
JVM辫封、DVM(Dalvik VM)和ART虛擬機(jī)對比
阿里巴巴 說說 Android 虛擬機(jī)Dalvik與ART區(qū)別在哪里硝枉?
Dalvik虛擬機(jī)垃圾收集機(jī)制簡要介紹和學(xué)習(xí)計(jì)劃
Dalvik 虛擬機(jī)和 Sun JVM 在架構(gòu)和執(zhí)行方面有什么本質(zhì)區(qū)別?
ART運(yùn)行時(shí)垃圾收集機(jī)制簡要介紹和學(xué)習(xí)計(jì)劃
Android 虛擬機(jī) Vs Java 虛擬機(jī)
Android GC原理探究
JVM倦微、DVM(Dalvik VM)和ART虛擬機(jī)對比

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妻味,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子欣福,更是在濱河造成了極大的恐慌责球,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拓劝,死亡現(xiàn)場離奇詭異雏逾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凿将,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門校套,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人牧抵,你說我怎么就攤上這事笛匙。” “怎么了犀变?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵妹孙,是天一觀的道長。 經(jīng)常有香客問我获枝,道長蠢正,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任省店,我火速辦了婚禮嚣崭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懦傍。我一直安慰自己雹舀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布粗俱。 她就那樣靜靜地躺著说榆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上签财,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天串慰,我揣著相機(jī)與錄音,去河邊找鬼唱蒸。 笑死邦鲫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的油宜。 我是一名探鬼主播掂碱,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怜姿,長吁一口氣:“原來是場噩夢啊……” “哼慎冤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起沧卢,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚁堤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后但狭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體披诗,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年立磁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呈队。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唱歧,死狀恐怖宪摧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颅崩,我是刑警寧澤几于,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站沿后,受9級特大地震影響沿彭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尖滚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一喉刘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漆弄,春花似錦睦裳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春鬓催,著一層夾襖步出監(jiān)牢的瞬間肺素,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工宇驾, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倍靡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓课舍,卻偏偏與公主長得像塌西,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子筝尾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355