還因為執行卡頓被懟?教你垃圾回收的黑科技!

Android蕭曉發表於2019-04-29

今天,我們來學習下 Android 中的垃圾回收機制。

大家應該知道,JVM 和 Dalvik 的垃圾回收機制實際並不完全相同。而垃圾回收機制一直都是工作和麵試中的必備技能,對 GC 有深入的理解,才能在程式碼層面更好地去減少 GC 的發生,畢竟每次 GC 都會對主執行緒的執行造成一定的卡頓,從而影響到使用者體驗。

1前言

本節將介紹支付寶 Android 客戶端啟動速度優化下的「垃圾回收」具體思路。

應用啟動時間是移動 App 一個重要的使用者體驗環節,相對於普通的移動 App,支付寶過於龐大,必然會影響啟動速度,一些常規的優化手段在支付寶中已經做得比較完善了,本篇文章嘗試從 GC 的層面來進一步優化支付寶的啟動速度。

2背景

相對於 C 語言來說,Java 語言有一些特性,例如開發人員不用考慮記憶體的分配和回收,然而,程式記憶體管理又是必不可少的環節,妥協的結果是 Java 語言的設計者們把物件分配和回收放到了 Java虛擬機器,這裡希望明確一個概念:GC 是有代價的,這個代價包括:阻塞 Java 程式的執行,佔用 CPU 資源,佔用額外記憶體等,谷歌的工程師意識到了 GC 對應用的影響,所以把 GC 的日誌預設輸出到了 Logcat,我們經常能夠看到 Logcat 裡輸出以下幾種 GC 日誌:

1.GC_EXPLICIT: Dalivk 給開發人員提供的主動觸發 GC 的 API,讀者可以參看 Google Maps 的設計來體會這個 API 的用法

2.GC_FOR _ALLOCK: 是分配物件失敗時觸發的 GC,這個 GC 會將應用所有的 Java 執行緒暫停執行,直到 GC 結束。

3.GC_CONCURRENT: 是 Java 虛擬機器根據堆的當前狀態觸發的 GC,這個 GC 在 Dalvik 單獨 GC 執行緒裡執行,在部分時間裡不影響應用 Java 執行緒的執行。 支付寶啟動是一個典型的關鍵路徑場景,我們希望看到儘可能少的 GC_ CONCURRENT(如果可能, GC_ FOR_ ALLOCK 也應該縮減到最少),然而,通過 Logcat 我們會看到非常糟糕的 GC 行為—大量的 GC_ FOR_ ALLOCK 以及觸目驚心的 Java 執行緒被 WAIT_ FOR_ CONCURRENT_ GC 阻塞。如下圖所示,通過簡單統計這些 GC 消耗的時間,我們能夠得出 GC 嚴重影響應用啟動時間的結論。

還因為執行卡頓被懟?教你垃圾回收的黑科技!

3設計思路

支付寶是 Android 系統的一個應用程式,如何能夠通過影響 Dalvik 的 GC 行為來縮短啟動時間呢?這個問題可以分解為兩步:

1.支付寶是否能影響自身 Dalvik 的行為

2.如何改進 Dalvik,縮短啟動時間

第一個問題答案是肯定的,Android 系統的設計思路是每個 Android 應用程式都有獨立的 Dalvik 例項,應用啟動後可以修改自己的程式空間裡的程式碼和資料,因此支付寶通過修改記憶體中的 Dalvik 庫檔案 libdvm.so 影響 Dalvik 的行為。

第二個問題的難點在於投入產出比:修改程式空間的程式碼和資料是面向二進位制,難度遠遠大於原始碼,也就是說稍微複雜的Dalvik改進工作是不可能的。

基於以上兩點,提出了一種設想:啟動時 GC 抑制,允許堆一直增長,直到開發人員主動停止 GC 抑制或者 OOM 停止 GC 抑制,這是一種"空間換時間"策略,用更多的記憶體消耗來換取啟動時間的縮短,這種策略可行有兩個前提:一是裝置廠商沒有加密記憶體中的 Dalvik 庫檔案,二是裝置廠商沒有改動 Google 的 Dalvik 原始碼(或者少量的改動),理論上通過白名單的方式可以覆蓋所有裝置,但是實現和維護成本都非常高。

4GC 抑制的實現

GC 抑制的前提是 Dalvik 比較熟悉,知道如何改變 GC 的行為,解決方案大致如下:首先在原始碼級別找到抑制GC的修改方法,例如改變跳轉分支,其次,在二進位制程式碼裡找到 A 分支條件跳轉的"指令指紋",以及用於改變分支的二進位制程式碼,假設為 override_A,應用啟動後掃描記憶體中的 libdvm.so,根據"指令指紋"定位到修改位置,然後用 override_A 覆蓋,這裡需要注意的是,"指令指紋"的定義需要有一些編譯器和 arm 指令集知識,實現 GC 抑制主要實現了以下 4 個部分:

  • 取消 softlimit 檢測

  • 取消 GC 執行緒的喚醒

  • 取消 GC 例程函式

  • OOM 停止 GC 抑制的實現

  • 取消 softlimit 檢測:

取消 softlimit 檢測的目的是最大限度的分配物件,下圖為 softlimit 檢查對應的 arm 指令片段,位於 dvmHeapSourceAlloc 函式中,OXE057 對應於"return NULL"的分支,如果我們想永遠不進入"return NULL"分支,可以改變 cmp 指令的結果,在具體實現裡我們把"0X42"作為"指令指紋"來識別而且修改為 "cmp r0, r0",這樣就可以實現取消 softlimit 檢查。

還因為執行卡頓被懟?教你垃圾回收的黑科技!

  • 取消 GC 執行緒喚醒

取消 GC 執行緒喚醒的目的是防止 GC 執行緒頻繁喚醒導致的執行緒抖動。下圖是對應的 C++ 程式碼和 arm 指令片段,這段程式碼同樣位於 dvmHeapSourceAlloc 函式中。在具體實現裡我們會依次掃描 libdvm.so的 dynstr、dynsym、rel.plt 和 plt 區域獲取 pthreadcondsignal@plt 的地址,然後遍歷 dvmHeapSourceAlloc 中的所有分支跳轉,計算跳轉目的地址。

如果發現 pthreadcondsignal@plt 和當前分支跳轉目的地址配置,擦除這條指令即可。

還因為執行卡頓被懟?教你垃圾回收的黑科技!

  • 取消 GC 例程函式

取消 GC 例程函式採用鉤子技術來實現,我們將 GC 抑制封裝成了兩個 native 介面 doStartSuppressGCdoStopSuppressGC;並且進一步封裝為 JNI 介面,便於開發者在 Java 裡呼叫。一般的應用方式是,開發者通過日誌看到支付寶在某個場景會觸發大量的 GC 且這個 GC 影響使用者體驗(響應時間慢或者動畫卡頓),然後在這個場景前後插入 doStartSuppressGCdoStopSuppressGC

以支付寶冷啟動場景為例,我們在容器 Quinox 的 attachBaseContext 函式裡插入 doStartSuppressGC,在首頁載入結束時插入 doStopSuppressGC

  • OOM 停止 GC 抑制的實現

如果僅僅考慮在支付寶啟動過程中抑制 GC,不需要考慮 OOM 停止 GC 抑制的實現,因為支付寶啟動不足以觸發 OOM。但是我們希望 GC 抑制成為一個基礎模組,能夠應用到更多場景中。如果程式在呼叫 doStopSuppressGC 前觸發了 OOM,則需要在 OOM 發生前停止 GC 抑制。和前面簡單的改變分支跳轉方向不同,需要在 OOM 發生前注入一個新的的分支跳轉,這個新分支的程式碼由我們來實現。新分支主要功能是,呼叫 doStopSuppressGC,然後去掉注入的新分支,最後跳回 Dalvik 執行 OOM。

還因為執行卡頓被懟?教你垃圾回收的黑科技!

  • 實現同樣採用傳統的鉤子技術。在鉤子函式 dvmCollectGarbageInternal 裡:

當條件不滿足時直接返回,達到取消 GC 的目的;

條件滿足時,取消鉤子且執行原來的 dvmCollectGarbageInternal。

實現中使用了開源的二進位制注入框架:github.com/crmulliner/…

這裡需要注意的是,在熱點函式裡使用這個框架提供的 pre_hook 和 post_hook 的效能開銷非常大。本文裡的設計只會用到一次 pre_hook,所以不存在效能問題。

看到的這裡讀者可能會問,這種通過“指令指紋”的方式靠譜麼?我的答案是,漏判不影響正確性,誤判理論上存在但概率極小(誤判指“指令指紋”定位到錯誤程式碼位置)。即使誤判發生了,我們還有最後一層保障——基礎架構組同學實現的容災機制。當誤判導致程式異常無法完成正常啟動時,重啟支付寶而且在後續的啟動中直接放棄 GC 抑制。

5效果

還因為執行卡頓被懟?教你垃圾回收的黑科技!

上圖的啟動時間的資料是在內部的 Android 4.x 測試裝置上獲得的(沒有標註 release 表示 debug 版本)。從圖表上來看,支付寶客戶端的啟動時間縮短了 15%~30%。

本人Java開發4年Android開發5年,定期分享Android高階技術及經驗分享,歡迎大家關注~(喜歡文章的點個贊鼓勵下叭~謝謝。)

讀者福利

  • Android前沿技術—元件化框架設計

    Android前沿技術

  • BAT主流Android高階架構技術大綱+學習路線+資料分享

架構技術詳解,學習路線與資料分享都在部落格這篇文章裡《“寒冬未過”,阿里P9架構分享Android必備技術點,讓你offer拿到手軟!》 (包括自定義控制元件、NDK、架構設計、混合式開發工程師(React native,Weex)、效能優化、完整商業專案開發等)

  • 全套體系化高階架構視訊;七大主流技術模組,視訊+原始碼+筆記

架構.jpg

相關文章