讀書筆記,如需轉載,請註明做者:Yuloran (t.cn/EGU6c76)android
本文分爲兩部分,第一部分爲 《Garbage Collection in Android》 的翻譯,第二部分簡介 Android 虛擬機與 Java 虛擬機的差異。git
Colt McAnlis,Google 開發工程師。爲便於寫做,筆者將以第一人稱視角對視頻內容進行概述。github
不少高性能語言,如 C 和 C++ 都須要開發人員手動管理內存的分配與釋放,但在代碼量很大、業務邏輯很複雜時,很容易忘記釋放已分配的內存,進而致使內存泄露。位於 Android Runtime 的 Dalvik(< Android 5.0) 或 ART(≥ Android 5.0)虛擬機的自動垃圾回收機制,將開放人員從手動管理內存的工做方式中解放了出來,從而提升了工做效率。但同時也隱藏了性能陷進,其中最須要關注的就是如何分配以及使用內存。算法
自動內存管理機制稱爲 Garbage Collection,是 John McCarthy 於1959 年提出的概念,用於解決 LISP 語言中的問題。它主要涉及兩個原則:性能優化
想象一下,你分配了 20,000 個對象,那麼怎麼識別哪些對象是再也不訪問的,或者何時應該觸發 GC 事件呢?bash
這真是太難了!好在,自 GC 概念提出以後的 50 年裏,咱們一直致力於提高垃圾收集器的性能,這也是爲何 Android 的垃圾回收器比 John McCarthy 提出的複雜的多的緣由。架構
事實上,Android 系統根據所分配對象的類型以及 GC 時系統如何管理這些對象,將進程所使用的內存分紅了多個空間。新分配的對象位於什麼空間,取決於你的 Android Runtime 是什麼版本,5.0 以上是 ART(Android Runtime)虛擬機,5.0如下是 Dalvik 虛擬機。併發
筆者注:上圖具體含義見下文差別分析。工具
每一個空間都有大小限制(Set Size),系統會跟蹤整個程序所佔用的內存大小。當程序佔用內存達到必定程度時,系統就會觸發 GC 事件回收內存,以便未來分配給其它對象:佈局
GC 事件在 Dalvik 虛擬機和 ART 虛擬機上的表現也不盡相同。在 Dalvik 虛擬機中,不少 GC 事件都是 "Stop the World Event":
而 ART 虛擬機擴展了並行 GC 算法,消除了大的 GC 停頓時間,可是在重要步驟上,仍是會有短暫的停頓:
儘管系統工程師作了大量優化來提高 GC 速度來減小卡頓,可是你的 App 仍然可能存在性能問題。咱們知道,Android 系統每 16 ms 就要渲染一幀,因此在一幀時間內,GC 時間越長,留給業務邏輯的時間就越短:
若是你的 GC 過於頻繁(好比在循環中建立了大量臨時對象)或 GC 時間過長,就會致使 1 幀的處理時間超過 16 ms 上限,這就會給用戶形成卡頓、掉幀的視覺效果:
好在,Android Studio 的 Profiler 工具,能夠用來查看內存使用狀況以及內存分配狀況。
不過,內存優化就是提及來簡單,作起來難,因此你還須要觀看如下視頻來掌握更多的性能優化知識:
其實筆者主要想關注的這幾個虛擬機在內存佈局及 GC 算法方面的差別,至於 JVM、Dalvik、ART 各自對應的可執行文件格式(.jar、.dex、.elf)、字節碼結構(class、dex、elf)這顯然是不一樣的。奈何關於 Android 虛擬機內存佈局網上資料甚少,大部分只圍繞運行時堆內存的分配及其 GC 算法來說,沒有涉及虛擬機棧(方法執行模型)、常量池、方法區,因此這部分無法跟 Java 虛擬機進行對比。不過,萬物之間是存在廣泛聯繫的,沒有東西能夠憑空產生。既然 Java 虛擬機的方法執行模型都能跟 C 語言在概念上很類似,Dalvik 和 ART 天然也能夠照此理解,細節上確定是不同的,畢竟指令集都不同。真想深究,只能看 虛擬機源碼 了。
不鑽牛角尖了,頭疼。再來看下這個圖:
這圖描述的就是 Dalvik 和 ART 虛擬機對運行時堆的空間劃分,這個在源碼中都有對應的實現。 上圖具體含義可參考:
或者使用 看雲 閱讀更方便。
總的來講,就是 Android 虛擬機沒有使用 HotSpot 虛擬機所採用的分代收集算法,而是採用了標記-清除或者標記-複製算法,這個能夠在編譯系統時指定,可參考《Android Garbage Collection/dalvik GC》,不過通常都是標記清除算法。
如下摘自 《Android虛擬機之Dalvik虛擬機》:
- 內存管理
◇ Java Object Heap 大小受限,如:16M/24M/32M/48M
◇ Bitmap Memory(External Memroy):大小計入 Java Object Heap
◇ Native Heap:大小不受限- 垃圾收集(GC)
◇ Mark:使用RootSet標記對象引用
◇ Sweep:回收沒有被引用的對象- GingerBread(Android 2.3)以前
◇ Stop-the-word:也就是垃圾收集線程在執行的時候,其它的線程都中止
◇ Full heap collection:也就是一次收集徹底部的垃圾,一次垃圾收集形成的程序停止時間一般都大於 100ms- GingerBread(Android 2.3)以後
◇ Cocurrent:也就是大多數狀況下,垃圾收集線程與其它線程是併發執行的
◇ Partial collection:也就是一次可能只收集一部分垃圾,一次垃圾收集形成的程序停止時間一般都小於 5ms
ART 虛擬機對並行 GC 進行了擴展,將堆內存劃分紅更多不一樣類型的具體空間,使用不一樣的 GC 算法以得到更短的 GC 停頓時間。
Dalvik 虛擬機,是 Google 等廠商合做開發的 Android 移動設備平臺的核心組成部分之一。它能夠支持已轉換爲 .dex(即「Dalvik Executable」)格式的 Java 應用程序的運行。.dex 格式是專爲 Dalvik 設計的一種壓縮格式,適合內存和處理器速度有限的系統。Dalvik 由 Dan Bornstein 編寫的,名字來源於他的祖先曾經居住過的小漁村達爾維克(Dalvík),位於冰島 Eyjafjörður。
大多數虛擬機包括 JVM 都是一種堆棧機器,而 Dalvik 虛擬機則是寄存器機。兩種架構各有優劣,通常而言,基於堆棧的機器須要更多指令,而基於寄存器的機器指令更長。
從 Android 5.0 版起,Android Runtime(ART)取代 Dalvik 成爲系統內默認虛擬機。
差別:
- Dalvik 虛擬機早期並無使用即時編譯(JIT)技術。從 Android 2.2 開始, Dalvik 虛擬機也支持 JIT.
- Dalvik 虛擬機有本身的字節碼,並不是使用 Java 字節碼。
- Dalvik 基於寄存器,而 JVM 基於堆棧。
- Dalvik VM 透過 Zygote 進行類別的預加載,Zygote 會完成虛擬機的初始化,也是與 JVM 不一樣之處。
Dalvik 虛擬機採用 Mark-Sweep 算法,不帶壓縮整理(Compact),因此比 Java 虛擬機更簡單一些。
(a)GC 前的狀態。示例中有一個 GC Root,全部對象都未被標記。
(b)GC 標記後的狀態。在標記階段,全部活動對象(Active Objects)都會被標記。
(c)GC 清除後的狀態。全部垃圾已被回收,而且全部活動對象的標記狀態都被重置爲 false。
在 Dalvik(而不是 ART)中,每次垃圾回收都會將如下信息打印到 logcat 中:
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
複製代碼
示例:
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/5261K, paused 2ms+2ms
複製代碼
垃圾回收緣由
什麼觸發了垃圾回收以及是哪一種回收。可能出現的緣由包括:
釋放量
今後次垃圾回收中回收的內存量。
堆統計數據
堆的可用空間百分比與(活動對象數量)/(堆總大小)。
外部內存統計數據
API 級別 10 及更低級別的外部分配內存(已分配內存量)/(發生回收的限值)。
暫停時間
堆越大,暫停時間越長。併發暫停時間顯示了兩個暫停:一個出如今回收開始時,另外一個出如今回收快要完成時。 在這些日誌消息積聚時,請注意堆統計數據的增大(上面示例中的 3571K/9991K 值)。若是此值繼續增大,可能會出現內存泄漏。
Android Runtime(縮寫爲ART),是一種在 Android 操做系統上的運行環境,由 Google 公司研發,並在 2013 年做爲 Android 4.4 系統中的一項測試功能正式對外發布,在 Android 5.0 及後續 Android 版本中做爲正式的運行時庫取代了以往的 Dalvik 虛擬機。ART 可以把應用程序的字節碼轉換爲機器碼,是 Android 所使用的一種新的虛擬機。它與 Dalvik 的主要不一樣在於:Dalvik 採用的是 JIT 技術,而 ART 採用 Ahead-of-time(AOT)技術。ART 同時也改善了性能、垃圾回收(Garbage Collection)、應用程序出錯以及性能分析。
JIT 最先在 Android 2.2 系統中引進到 Dalvik 虛擬機中,在應用程序啓動時,JIT 經過進行連續的性能分析來優化程序代碼的執行,在程序運行的過程當中,Dalvik 虛擬機在不斷的進行將字節碼編譯成機器碼的工做。與 Dalvik 虛擬機不一樣的是,ART 引入了 AOT 這種預編譯技術,在應用程序安裝的過程當中,ART 就已經將全部的字節碼從新編譯成了機器碼。應用程序運行過程當中無需進行實時的編譯工做,只須要進行直接調用。所以,ART 極大的提升了應用程序的運行效率,同時也減小了手機的電量消耗,提升了移動設備的續航能力,在垃圾回收等機制上也有了較大的提高。爲了保證向下兼容,ART 使用了相同的 Dalvik 字節碼文件(dex),即在應用程序目錄下保留了 dex 文件供舊程序調用然而 .odex 文件則替換成了可執行與可連接格式(ELF)可執行文件。一旦一個程序被 ART 的 dex2oat 命令編譯,那麼這個程序將會指經過 ELF 可執行文件來運行。所以,相對於 Dalvik 虛擬機模式,ART 模式下 Android 應用程序的安裝須要消耗更多的時間,同時也會佔用更大的儲存空間(指內部儲存,用於儲存編譯後的代碼),但節省了不少 Dalvik 虛擬機用於實時編譯的時間。
Google 公司在 Android 4.4 中帶來的 ART 模式僅僅是 ART 的一個預覽版,系統默認仍然使用的是 Dalvik 虛擬機,4.4 上面提供的預覽版 ART 相對於 Android 5.0 之後的 ART 運行時庫有較大的不一樣,尤爲體如今兼容性上。
與 Dalvik 不一樣,ART 不會爲未明確請求的垃圾回收記錄消息。只有在認爲垃圾回收速度較慢時纔會打印垃圾回收。更確切地說,僅在垃圾回收暫停時間超過 5ms 或垃圾回收持續時間超過 100ms 時。若是應用未處於可察覺的暫停進程狀態,那麼其垃圾回收不會被視爲較慢。始終會記錄顯式垃圾回收。
ART 會在其垃圾回收日誌消息中包含如下信息:
I/art: <GC_Reason> <GC_Name> <Objects_freed>(<Size_freed>) AllocSpace Objects, <Large_objects_freed>(<Large_object_size_freed>) <Heap_stats> LOS objects, <Pause_time(s)>
複製代碼
示例:
I/art : Explicit concurrent mark sweep GC freed 104710(7MB) AllocSpace objects, 21(416KB) LOS objects, 33% free, 25MB/38MB, paused 1.230ms total 67.216ms
複製代碼
垃圾回收緣由
什麼觸發了垃圾回收以及是哪一種回收。可能出現的緣由包括:
垃圾回收名稱
ART 具備能夠運行的多種不一樣的垃圾回收。
釋放的對象
這次垃圾回收從非大型對象空間回收的對象數量。
釋放的大小
這次垃圾回收從非大型對象空間回收的字節數量。
釋放的大型對象
這次垃圾回收從大型對象空間回收的對象數量。
釋放的大型對象大小
這次垃圾回收從大型對象空間回收的字節數量。
堆統計數據
空閒百分比與(活動對象數量)/(堆總大小)。
暫停時間
一般狀況下,暫停時間與垃圾回收運行時修改的對象引用數量成正比。當前,ART CMS 垃圾回收僅在垃圾回收即將完成時暫停一次。移動的垃圾回收暫停時間較長,會在大部分垃圾回收期間持續出現。
若是您在 logcat 中看到大量的垃圾回收,請注意堆統計數據的增大(上面示例中的 25MB/38MB 值)。若是此值繼續增大,且始終沒有變小的趨勢,則可能會出現內存泄漏。或者,若是您看到緣由爲「Alloc」的垃圾回收,那麼您的操做已經快要達到堆容量,而且將很快出現 OOM 異常。
這個網頁好像沒有英文版,可是有的中文解釋又很彆扭,好比「映像空間」,這是什麼鬼?其實就是 Image Space...