Android 虛擬機 Vs Java 虛擬機

讀書筆記,如需轉載,請註明做者:Yuloran (t.cn/EGU6c76)android

前言

本文分爲兩部分,第一部分爲 《Garbage Collection in Android》 的翻譯,第二部分簡介 Android 虛擬機與 Java 虛擬機的差異。git

Garbage Collection in Android

演講人介紹

Colt McAnlis,Google 開發工程師。爲便於寫做,筆者將以第一人稱視角對視頻內容進行概述。github

自動內存管理的陷進

不少高性能語言,如 C 和 C++ 都須要開發人員手動管理內存的分配與釋放,但在代碼量很大、業務邏輯很複雜時,很容易忘記釋放已分配的內存,進而致使內存泄露。位於 Android Runtime 的 Dalvik(< Android 5.0) 或 ART(≥ Android 5.0)虛擬機的自動垃圾回收機制,將開放人員從手動管理內存的工做方式中解放了出來,從而提升了工做效率。但同時也隱藏了性能陷進,其中最須要關注的就是如何分配以及使用內存。算法

GC 概念由來

自動內存管理機制稱爲 Garbage Collection,是 John McCarthy 於1959 年提出的概念,用於解決 LISP 語言中的問題。它主要涉及兩個原則:性能優化

  1. 找出再也不訪問的對象;
  2. 回收這些對象佔用的資源。

GC 實現的難點

想象一下,你分配了 20,000 個對象,那麼怎麼識別哪些對象是再也不訪問的,或者何時應該觸發 GC 事件呢?bash

這真是太難了!好在,自 GC 概念提出以後的 50 年裏,咱們一直致力於提高垃圾收集器的性能,這也是爲何 Android 的垃圾回收器比 John McCarthy 提出的複雜的多的緣由。架構

Android GC 簡介

事實上,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 致使的掉幀

儘管系統工程師作了大量優化來提高 GC 速度來減小卡頓,可是你的 App 仍然可能存在性能問題。咱們知道,Android 系統每 16 ms 就要渲染一幀,因此在一幀時間內,GC 時間越長,留給業務邏輯的時間就越短:

若是你的 GC 過於頻繁(好比在循環中建立了大量臨時對象)或 GC 時間過長,就會致使 1 幀的處理時間超過 16 ms 上限,這就會給用戶形成卡頓、掉幀的視覺效果:

內存分析工具

好在,Android Studio 的 Profiler 工具,能夠用來查看內存使用狀況以及內存分配狀況。

總結

不過,內存優化就是提及來簡單,作起來難,因此你還須要觀看如下視頻來掌握更多的性能優化知識:

連接

Android 虛擬機與 Java 虛擬機的差別

其實筆者主要想關注的這幾個虛擬機在內存佈局及 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 虛擬機

wiki

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 不一樣之處。

標記-清除算法(Mark-Sweep Algorithm)

Dalvik 虛擬機採用 Mark-Sweep 算法,不帶壓縮整理(Compact),因此比 Java 虛擬機更簡單一些。

(a)GC 前的狀態。示例中有一個 GC Root,全部對象都未被標記。

(b)GC 標記後的狀態。在標記階段,全部活動對象(Active Objects)都會被標記。

(c)GC 清除後的狀態。全部垃圾已被回收,而且全部活動對象的標記狀態都被重置爲 false。

GC 觸發時機

  • 即將產生 OOM 時
  • 堆內存使用達到上限時(系統可配)
  • 顯示調用 gc()

GC 日誌

在 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
複製代碼

垃圾回收緣由

什麼觸發了垃圾回收以及是哪一種回收。可能出現的緣由包括:

  • GC_CONCURRENT:在您的堆開始佔用內存時能夠釋放內存的併發垃圾回收。
  • GC_FOR_MALLOC:堆已滿而系統不得不中止您的應用並回收內存時,您的應用嘗試分配內存而引發的垃圾回收。
  • GC_HPROF_DUMP_HEAP:當您請求建立 HPROF 文件來分析堆時出現的垃圾回收。
  • GC_EXPLICIT:顯式垃圾回收,例如當您調用 gc() 時(您應避免調用,而應信任垃圾回收會根據須要運行)。
  • GC_EXTERNAL_ALLOC:這僅適用於 API 級別 10 及更低級別(更新版本會在 Dalvik 堆中分配任何內存)。外部分配內存的垃圾回收(例如存儲在原生內存或 NIO 字節緩衝區中的像素數據)。

釋放量

今後次垃圾回收中回收的內存量。

堆統計數據

堆的可用空間百分比與(活動對象數量)/(堆總大小)。

外部內存統計數據

API 級別 10 及更低級別的外部分配內存(已分配內存量)/(發生回收的限值)。

暫停時間

堆越大,暫停時間越長。併發暫停時間顯示了兩個暫停:一個出如今回收開始時,另外一個出如今回收快要完成時。 在這些日誌消息積聚時,請注意堆統計數據的增大(上面示例中的 3571K/9991K 值)。若是此值繼續增大,可能會出現內存泄漏。

ART 虛擬機

wiki

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 運行時庫有較大的不一樣,尤爲體如今兼容性上。

GC 日誌

與 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
複製代碼

垃圾回收緣由

什麼觸發了垃圾回收以及是哪一種回收。可能出現的緣由包括:

  • Concurrent:不會暫停應用線程的併發垃圾回收。此垃圾回收在後臺線程中運行,並且不會阻止分配。
  • Alloc:您的應用在堆已滿時嘗試分配內存引發的垃圾回收。在這種狀況下,分配線程中發生了垃圾回收。
  • Explicit:由應用明確請求的垃圾回收,例如,經過調用 gc() 或 gc()。與 Dalvik 相同,在 ART 中,最佳作法是您應信任垃圾回收並避免請求顯式垃圾回收(若是可能)。不建議使用顯式垃圾回收,由於它們會阻止分配線程並沒必要要地浪費 CPU 週期。若是顯式垃圾回收致使其餘線程被搶佔,那麼它們也可能會致使卡頓(應用中出現間斷、抖動或暫停)。
  • NativeAlloc:原生分配(如位圖或 RenderScript 分配對象)致使出現原生內存壓力,進而引發的回收。
  • CollectorTransition:由堆轉換引發的回收;此回收由運行時切換垃圾回收引發。回收器轉換包括將全部對象從空閒列表空間複製到碰撞指針空間(反之亦然)。當前,回收器轉換僅在如下狀況下出現:在 RAM 較小的設備上,應用將進程狀態從可察覺的暫停狀態變動爲可察覺的非暫停狀態(反之亦然)。
  • HomogeneousSpaceCompact[/ˌhɒmə(ʊ)'dʒiːnɪəs/]:齊性空間壓縮是空閒列表空間到空閒列表空間壓縮,一般在應用進入到可察覺的暫停進程狀態時發生。這樣作的主要緣由是減小 RAM 使用量並對堆進行碎片整理。
  • DisableMovingGc:這不是真正的垃圾回收緣由,但請注意,發生併發堆壓縮時,因爲使用了 GetPrimitiveArrayCritical,回收遭到阻止。通常狀況下,強烈建議不要使用 GetPrimitiveArrayCritical,由於它在移動回收器方面具備限制。
  • HeapTrim:這不是垃圾回收緣由,但請注意,堆修剪完成以前回收會一直受到阻止。

垃圾回收名稱

ART 具備能夠運行的多種不一樣的垃圾回收。

  • Concurrent mark sweep (CMS):整個堆回收器,會釋放和回收映像空間(Image Space)之外的全部其餘空間。
  • Concurrent partial mark sweep:幾乎整個堆回收器,會回收除了映像空間(Image Space)和 zygote 空間之外的全部其餘空間。
  • Concurrent sticky mark sweep:生成回收器,只能釋放自上次垃圾回收以來分配的對象。此垃圾回收比完整或部分標記清除運行得更頻繁,由於它更快速且暫停時間更短。
  • Marksweep + semispace:非併發、複製垃圾回收,用於堆轉換以及齊性空間壓縮(對堆進行碎片整理)。

釋放的對象

這次垃圾回收從非大型對象空間回收的對象數量。

釋放的大小

這次垃圾回收從非大型對象空間回收的字節數量。

釋放的大型對象

這次垃圾回收從大型對象空間回收的對象數量。

釋放的大型對象大小

這次垃圾回收從大型對象空間回收的字節數量。

堆統計數據

空閒百分比與(活動對象數量)/(堆總大小)。

暫停時間

一般狀況下,暫停時間與垃圾回收運行時修改的對象引用數量成正比。當前,ART CMS 垃圾回收僅在垃圾回收即將完成時暫停一次。移動的垃圾回收暫停時間較長,會在大部分垃圾回收期間持續出現。

若是您在 logcat 中看到大量的垃圾回收,請注意堆統計數據的增大(上面示例中的 25MB/38MB 值)。若是此值繼續增大,且始終沒有變小的趨勢,則可能會出現內存泄漏。或者,若是您看到緣由爲「Alloc」的垃圾回收,那麼您的操做已經快要達到堆容量,而且將很快出現 OOM 異常。

官網 GC 日誌原文

這個網頁好像沒有英文版,可是有的中文解釋又很彆扭,好比「映像空間」,這是什麼鬼?其實就是 Image Space...

相關文章
相關標籤/搜索