虛擬機(一):Java 篇
虛擬機(二):Android 篇
Dalvik和JVM區別
- Dalvik 基于寄存器,而 JVM 基于棧。
- 基于棧的架構具有更好的可移植性,因為其實現不依賴于物理寄存器
- 基于棧的架構通常指令更短,因為其操作不需要指定操作數和結果的地址
- 基于寄存器的架構通常運行速度更快,因為有寄存器的支撐
- 基于寄存器的架構通常需要較少的指令來完成同樣的運算,因為不需要進行壓棧和出棧
- 棧屬于內存,速度稍慢,空間更大。寄存器屬于CPU,速度更快,空間更小。
- dex
- dex格式是專為Dalvik設計的一種壓縮格式,可以減少整體文件尺寸,提高I/O操作的速度,適合內存和處理器速度有限的系統。
- dex文件格式相對來說更加的緊湊。dex文件按照類型(例如:常量,字段,方法)劃分,將同一類型的元素集中到一起并且去掉了重復項進行存放。這樣可以更大程度上避免重復,減少文件大小。
- 為了便于開發者分析dex文件中的內容,Android系統中內置了dexdump工具。借助這個工具,我們可以詳細了解到dex的文件結構和內容。oat文件也有對應的dump工具oatdump。
ART和Dalvik區別
- Dalvik是為32位設計的,不適用于64位CPU。
- 編譯
- Dalvik:通過dexopt的工具將 APK 中內容 DEX 轉化為 ODEX。在應用安裝后,利用JIT進行部分編譯,其他直接將字節碼存儲起來,在每次運行時,需要將字節碼編譯成機器語言。
- ART:通過dex2oat的工具將 APK 中內容 DEX 編譯成包含本地機器碼 OAT。應用在安裝后,會進行一次AOT(Ahead Of Time 運行前編譯)預編譯,將應用安裝包中的字節碼轉換成機器語言存儲在本地(系統只能運行二進制程序),這樣,應用在運行時,可以直接執行這些二進制程序。Art上應用啟動快,運行快,但是耗費更多存儲空間,安裝時間長,總的來說ART的功效就是"空間換時間"。
- 內存分配器 內存空間
- 垃圾回收
- 將GC的停頓由2次改成1次
- 在僅有一次的GC停頓中進行并行處理
- 前后臺劃分
JIT的回歸
- 解決系統、應用的安裝、升級慢的問題
- 編譯生成的Oat文件中,既包含了原先的Dex文件,又包含了編譯后的機器代碼。而實際上,對于用戶來說,并非會用到應用程序中的所有功能,因此很多時候編譯生成的機器碼是一直用不到的。
- Android 7.0 中,Google又為Android添加了即時 (JIT) 編譯器。JIT和AOT的配合,是取兩者之長,避兩者之短:在APK安裝時,并不是一次性將所有代碼全部編譯成機器碼。而是在實際運行過程中,對代碼進行分析,將熱點代碼編譯成機器碼,讓它可以在應用運行時持續提升 Android 應用的性能。
AOT和JIT配合:
- 最初在安裝應用程序的時候不執行任何AOT編譯。應用程序運行的前幾次都將使用解釋模式,并且經常執行的方法將被JIT編譯。
- 當設備處于空閑狀態并正在充電時,編譯守護進程會根據第一次運行期間生成的Profile文件對常用代碼運行AOT編譯。
- 應用程序的下一次重新啟動將使用Profile文件引導的代碼,并避免在運行時為已編譯的方法進行JIT編譯。在新運行期間得到JIT編譯的方法將被添加到Profile文件中,然后被編譯守護進程使用。
ART 內存模型
- ImageSpace
- 永遠不GC
- ZygoteSpace
- “full”gc條件下才會掃描該區域
- 本身沒有創建相關的內存資源,而是通過外部傳入的MemMap對象,作為內存資源,自身只是起到了一個管理作用
- 繼承自Zygote進程的資源存放地
- MallocSpace
- 每次GC都會嘗試清除該區域
- DlmallocSpace:采用的是dlmalloc內存分配管理模型,它是一個開源庫,也是c語言malloc調用的具體實現。
- RosAllocSpace:采用的是谷歌自己的內存分配rosalloc完成。rosalloc是一種動態分配內存算法,專門為了art虛擬機做了適配,其實它是一種多粒度內存分配算法,ros的意思就是run of slot,可以理解為一個run是RosAllocSpace中內存分配的單元,每個Run有自己的內存分配粒度(slot)
- LargeObjectSpace
- 每次GC都會嘗試清除該區域
- 采用mmap去分配內存空間
- 不連續
- large_object_threshold_默認為12kb
- RegionSpace
- 每次GC都會嘗試清除該區域
- 內存塊分配
- 內存對齊,由屬性kRegionSize決定(每個Region默認1m)
- 每個Region本身還對應一個狀態RegionState
- Copying Collection(拷貝垃圾回收機制)的內存分配模型
- BumpPointerSpace
- 每次GC都會嘗試清除該區域
- 順序分配
內存分配器:
- Davlik虛擬機使用的是傳統的 dlmalloc 內存分配器進行內存分配。這個內存分配器是Linux上很常用的
- Google為ART虛擬機開發了一個基于位圖的新內存分配器:RoSalloc,它的全稱是Rows of Slots allocator。RoSalloc相較于dlmalloc來說,在多線程環境下有更好的支持,具有分片鎖:在dlmalloc中,分配內存時使用了全局的內存鎖,這就很容易造成性能不佳。而在RoSalloc中,當分配規模較小時可添加線程的本地緩沖區,允許在線程本地區域存儲小對象,這就是避免了全局鎖的等待時間。
- ART虛擬機中,這兩種內存分配器都有使用。
- RegionTLAB:從 Android 8 (Oreo) 開始,默認垃圾回收方案是并發復制 (CC)。CC 支持使用名為“RegionTLAB”的觸碰指針分配器。此分配器可以向每個應用線程分配一個線程本地分配緩沖區 (TLAB),這樣,應用線程只需觸碰“棧頂”指針,而無需任何同步操作,即可從其 TLAB 中將對象分配出去。
- RosAlloc 是基于空閑列表的分配器,與 RegionTLAB 相比,該分配器的分配成本較高。由于 CMS 很少進行壓縮,因此空閑對象可能會不連續,導致碎片更多。
ART GC
觸發垃圾回收:
- kGcCauseForAlloc 內存分配失敗
- kGcCauseBackground 后臺進程的垃圾回收,為了確保內存的充足
- kGcCauseExplicit 明確的System.gc()調用
- kGcCauseForNativeAlloc 由于native的內存分配
- kGcCauseCollectorTransition 垃圾收集器發生了切換
- kGcCauseHomogeneousSpaceCompact 當前景和后臺收集器都是CMS時,發生了后臺切換
- kGcCauseClassLinker ClassLinker導致
拋開System.gc引起的主動gc,大部分GC由ConcurrentGCTask與分配時AllocInternalWithGc觸發
回收策略:
- Sticky 僅僅釋放上次GC之后創建的對象。基于“分代”的垃圾回收思想
- Partial 僅僅對應用程序的堆進行垃圾回收,但是不處理Zygote的堆
- Full 會對應用程序和Zygote的堆都會進行垃圾回收
綜述:
- ART 有多個不同的 GC 方案,這些方案包括運行不同垃圾回收器。在heap.cc的CollectGarbageInternal方法中,會根據當前的GC類型和原因,選擇合適的垃圾回收器,然后執行垃圾回收。
- CMS(Concurrent Mark Sweep,并發標記清除)方案,主要使用粘性(sticky)CMS 和部分(partial)CMS。
- 粘性(sticky)CMS
- 粘性CMS是ART的不移動(non-moving )分代垃圾回收器,增加了GC吞吐量。
- 它僅掃描堆中自上次 GC 后修改的部分,并且只能回收自上次GC后分配的對象。
- 不同于普通的分代GC,粘性 CMS 不會移動。年輕對象被保存在一個分配堆棧(基本上是 java.lang. Object 數組)中,而非為其設置一個專用區域。這樣可以避免移動所需的對象以維持低暫停次數,但缺點是容易在堆棧中加入大量復雜對象圖像而使堆棧變長。
- 前后臺
- 當應用將進程狀態更改為察覺不到卡頓的進程狀態(例如,后臺或緩存)時,ART 將暫停應用線程以執行堆壓縮。
- Compact類型的垃圾回收器便是“標記-壓縮”算法。這種類型的垃圾回收器,會在將對象清理之后,將最終還在使用的內存空間移動到一起,這樣可以既可以減少堆中的碎片,也節省了堆空間。但是由于這種垃圾回收器需要對內存進行移動,所以耗時較多,因此這種垃圾回收器適合于切換到后臺的應用。
- 垃圾回收器的決定會在Heap初始化的時候,選擇垃圾回收器,需要指定前臺垃圾回收器與后臺垃圾回收器
- 在前臺環境下,用戶對于卡頓會更加敏感,因此需要選擇更快的垃圾回收,而后臺環境下,卡頓不敏感,因此需要進行內存的整理,便于內存塊的整合
- android7前臺是CMS(Concurrent Mark Sweep,并發標記清除),后臺是HSC。
- 并發復制 (CC)
- 從 Android 8 (Oreo) 開始,默認方案是并發復制 (CC)。
- 通過在不暫停應用線程的情況下并發復制對象來執行堆碎片整理。這是在讀取屏障的幫助下實現的,讀取屏障會攔截來自堆的引用讀取,無需應用開發者進行任何干預。
- GC 只有一次很短的暫停,對于堆大小而言,該次暫停在時間上是一個常量。
- 在 Android 10 及更高版本中,CC 會擴展為分代 GC。輕松回收存留期較短的對象,并顯著延遲執行全堆 GC 的需要。