Android 性能優(yōu)化系列:抖音字節(jié)跳動技術(shù)團隊教你Java 內(nèi)存優(yōu)化

原文鏈接:https://mp.weixin.qq.com/s?__biz=MzI1MzYzMjE0MQ%3D%3D&chksm=e9d0c4c1dea74dd72482c94f936fa31d5609eff5f09b7ee2405e95eecba7ce00bbbb5657730b&idx=1&lang=zh_CN&mid=2247487267&scene=21&sn=64858e39d3c0ac3b3444213856f0d9a3&token=534750968#wechat_redirect

內(nèi)存作為計算機程序運行最重要的資源之一贰镣,需要運行過程中做到合理的資源分配與回收虏冻,不合理的內(nèi)存占用輕則使得用戶應(yīng)用程序運行卡頓、ANR榴嗅、黑屏拇惋,重則導(dǎo)致用戶應(yīng)用程序發(fā)生 OOM(out of memory)崩潰周偎。抖音作為一款用戶使用廣泛的產(chǎn)品,需要在各種機器資源上保持優(yōu)秀的流暢性和穩(wěn)定性撑帖,內(nèi)存優(yōu)化是必須要重視的環(huán)節(jié)蓉坎。

本文從抖音 Java OOM 內(nèi)存優(yōu)化的治理實踐出發(fā),嘗試給大家分享一下抖音團隊關(guān)于 Java 內(nèi)存優(yōu)化中的一些思考胡嘿,包括工具建設(shè)蛉艾、優(yōu)化方法論。

抖音 Java OOM 背景

在未對抖音內(nèi)存進行專項治理之前我們梳理了一下整體內(nèi)存指標的絕對值和相對崩潰衷敌,發(fā)現(xiàn)占比都很高勿侯。另外,內(nèi)存相關(guān)指標在去年春節(jié)活動時又再次激增達到歷史新高缴罗,所以整體來看內(nèi)存問題相當(dāng)嚴峻助琐,必須要對其進行專項治理。抖音這邊通過前期歸因面氓、工具建設(shè)以及投入一個雙月的內(nèi)存專項治理將整體 Java OOM 優(yōu)化了百分之 80兵钮。

Java OOM Top 堆棧歸因

在對抖音的 Java 內(nèi)存優(yōu)化治理之前我們先根據(jù)平臺上報的堆棧異常對當(dāng)前的 OOM 進行歸因,主要分為下面幾類:

圖片

圖 1. OOM 分類

其中 pthread_create 問題占到了總比例大約在百分之 50舌界,Java 堆內(nèi)存超限為百分之 40 多掘譬,剩下是少量的 fd 數(shù)量超限。其中 pthread_create 和 fd 數(shù)量不足均為 native 內(nèi)存限制導(dǎo)致的 Java 層崩潰禀横,我們對這部分的內(nèi)存問題也做了針對性優(yōu)化屁药,主要包括:

  • 線程收斂、監(jiān)控

  • 線程棧泄漏自動修復(fù)

  • FD 泄漏監(jiān)控

  • 虛擬內(nèi)存監(jiān)控柏锄、優(yōu)化

  • 抖音 64 位專項

治理之后 pthread_create 問題降低到了 0.02‰以下酿箭,這方面的治理實踐會在下一篇抖音 Native 內(nèi)存治理實踐中詳細介紹,大家敬請期待趾娃。本文重點介紹 Java 堆內(nèi)存治理缭嫡。

堆內(nèi)存治理思路

從 Java 堆內(nèi)存超限的分類來看,主要有兩類問題:

1. 堆內(nèi)存單次分配過大/多次分配累計過大抬闷。

觸發(fā)這類問題的原因有數(shù)據(jù)異常導(dǎo)致單次內(nèi)存分配過大超限妇蛀,也有一些是 StringBuilder 拼接累計大小過大導(dǎo)致等等耕突。這類問題的解決思路比較簡單,問題就在當(dāng)前的堆棧评架。

2. ****堆內(nèi)存累積分配觸頂眷茁。

這類問題的問題堆棧會比較分散,在任何內(nèi)存分配的場景上都有可能會被觸發(fā)纵诞,那些高頻的內(nèi)存分配節(jié)點發(fā)生的概率會更高上祈,比如 Bitmap 分配內(nèi)存。這類 OOM 的根本原因是內(nèi)存累積占用過多浙芙,而當(dāng)前的堆棧只是壓死駱駝的最后一根稻草登刺,并不是問題的根本所在。所以這類問題我們需要分析整體的內(nèi)存分配情況嗡呼,從中找到不合理的內(nèi)存使用(比如內(nèi)存泄露纸俭、大對象、過多小對象南窗、大圖等)揍很。

工具建設(shè)

工具思路

工欲善其事,必先利其器矾瘾。從上面的內(nèi)存治理思路看女轿,工具需要主要解決的問題是分析整體的內(nèi)存分配情況箭启,發(fā)現(xiàn)不合理的內(nèi)存使用(比如內(nèi)存泄露壕翩、大對象、過多小對象等)傅寡。

我們從線下和線上兩個維度來建設(shè)工具:

線下

線下工具是最先考慮的放妈,在研發(fā)和測試的時候能夠提前發(fā)現(xiàn)內(nèi)存泄漏問題。業(yè)界的主流工具也是這個思路荐操,比如 Android Studio Memory Profiler芜抒、LeakCanary、Memory Analyzer (MAT)托启。

我們基于 LeakCanary 核心庫在線下設(shè)計了一套自動分析上報內(nèi)存泄露的工具宅倒,主要流程如下:

圖片

圖 2.線下自動分析流程

抖音在運行了一段線下的內(nèi)存泄漏工具之后,發(fā)現(xiàn)了線下工具的各種弊端:

  1. 檢測出來的內(nèi)存泄漏過多屯耸,并且也沒有比較好的優(yōu)先級排序拐迁,研發(fā)消費不過來,歷史問題就一直堆積疗绣。另外也很難和業(yè)務(wù)研發(fā)溝通問題解決的收益线召,大家針對解決線下的內(nèi)存泄漏問題的 ROI(投入產(chǎn)出比)比較難對齊。
  2. 線下場景能跑到的場景有限多矮,很難把所有用戶場景窮盡缓淹。抖音用戶基數(shù)很大,我們經(jīng)常遇到一些線上的 OOM 激增問題,因為缺少線上數(shù)據(jù)而無從查起讯壶。
  3. Android 端的 HPORF 的獲取依賴原生的 Debug.dumpHporf料仗,dump 過程會掛起主線程導(dǎo)致明顯卡頓,線下使用體驗較差伏蚊,經(jīng)常會有研發(fā)反饋影響測試罢维。
  4. LeakCanary 基于 Shark 分析引擎分析,分析速度較慢丙挽,通常在 5 分鐘以上才能分析完成肺孵,分析過程會影響進程內(nèi)存占用。
  5. 分析結(jié)果較為單一颜阐,僅僅只能分析出 Fragment平窘、Activity 內(nèi)存泄露,像大對象凳怨、過多小對象問題導(dǎo)致的內(nèi)存 OOM 無法分析瑰艘。

線上

正是由于上述一些弊端,抖音最早的線下工具和治理流程并沒有起到什么太大作用肤舞,我們不得不重新審視一下紫新,工具建設(shè)的重心從線下轉(zhuǎn)成了線上。線上工具的核心思路是:在發(fā)生 OOM 或者內(nèi)存觸頂?shù)扔|發(fā)條件下李剖,dump 內(nèi)存的 HPROF 文件芒率,對 HPROF 文件進行分析,分析出內(nèi)存泄漏篙顺、大對象偶芍、小對象、圖片問題并按照泄露鏈路自動歸因德玫,將大數(shù)據(jù)問題按照用戶發(fā)生次數(shù)匪蟀、泄露大小、總大小等緯度排序宰僧,推進業(yè)務(wù)研發(fā)按照優(yōu)先級順序來建立消費流程材彪。為此我們研發(fā)了一套基于 HPORF 分析的線下、線上閉環(huán)的自動化分析工具 Liko(寓意 ko 內(nèi)存 Leak 問題)琴儿。

Liko 介紹

Liko 整體架構(gòu)

圖片

圖 3. Liko 架構(gòu)圖

整體架構(gòu)由客戶端段化、Server 端和核心分析引擎三部分構(gòu)成。

  • 客戶端

在客戶端完成 HPROF 數(shù)據(jù)采集和分析(針對端上分析模式)凤类,這里線上和線下策略不同穗泵。

線上:主要在 OOM 和內(nèi)存觸頂時通過用戶無感知 dump 來獲取 HPROF 文件,當(dāng) App 退出到后臺且內(nèi)存充足的情況進行分析谜疤,為了盡量減少對 App 運行時影響佃延,主要通過裁剪 HPROF 回傳進行分析现诀,為減輕服務(wù)器壓力,對部分比例用戶采用端上分析作為 Backup履肃。

線下:dump 策略配置較為激進仔沿,在 OOM、內(nèi)存觸頂尺棋、內(nèi)存激增封锉、監(jiān)測 Activity、Fragment 泄漏數(shù)量達到一定閾值多種場景下觸發(fā) dump膘螟,并實時在端上分析上傳至后臺并在本地自動生成 html 報表成福,幫助研發(fā)提前發(fā)現(xiàn)可能存在的內(nèi)存問題。

  • Server 端

Server 端根據(jù)線上回傳的大數(shù)據(jù)完成鏈路聚合荆残、還原奴艾、分配,并根據(jù)用戶發(fā)生次數(shù)内斯、泄露大小蕴潦、總大小等緯度促進研發(fā)測消費,對于回傳分析模式則會另外進行 HPORF 分析俘闯。

  • 分析引擎

基于 MAT 分析引擎完成內(nèi)存泄露潭苞、大對象、小對象真朗、圖片等自動歸因此疹,同時支持在線下自動生成 Html 報表。

Liko 流程圖

圖片

圖 4. Liko 流程圖

整體流程分為:

  1. Hprof 收集

  2. 分析時機

  3. 分析策略

Hprof 收集

收集過程我們設(shè)置了多種策略可以自由組合蜜猾,主要有 OOM秀菱、內(nèi)存觸頂振诬、內(nèi)存激增蹭睡、監(jiān)測 Activity、Fragment 泄漏數(shù)量達到一定閾值時觸發(fā)赶么,線下線上策略配置不同肩豁。

為了解決 dump 掛起進程問題,我們采用了子進程 dump+fileObsever 的方式完成 dump 采集和監(jiān)聽辫呻。

在 fork 子進程之前先 Suspend 獲取主進程中的線程拷貝清钥,通過 fork 系統(tǒng)調(diào)用創(chuàng)建子進程讓子進程擁有父進程的拷貝,然后 fork 出的子進程中調(diào)用 Hprof 的 DumpHeap 函數(shù)即可完成把耗時的 dump 操作在放在子進程放闺。由于 suspendresume 是系統(tǒng)函數(shù)祟昭,我們這里通過自研的 native hook 工具對 libart.so hook 獲取系統(tǒng)調(diào)用。由于寫入是在子進程完成的怖侦,我們通過 Android 提供的 fileObsever 文件寫入進行監(jiān)控獲取 dump 完成時機篡悟。

圖片

圖 5.子進程 dump 流程圖

Hprof 分析時機

為了達到分析過程對于用戶無感谜叹,我們在線上、線下配置了不同的分析時機策略搬葬,線下在 dump 分析完成后根據(jù)內(nèi)存狀態(tài)主動觸發(fā)分析荷腊,線上當(dāng)用戶下次冷啟退出應(yīng)用后臺且內(nèi)存充足的情況下觸發(fā)分析。

分析策略

分析策略我們提供了兩種急凰,一種在 Android 客戶端分析女仰,一種回傳至 Server 端分析,均通過 MAT 分析引擎進行分析抡锈。

端上分析
分析引擎

端上分析引擎的性能很重要疾忍,這里我們主要對比了 LeakCanary 的分析引擎 Shark 和 Haha 庫的 MAT。

圖片

圖 6. Shark VS MAT

我們在相同客戶端環(huán)境對 160M 的 HPROF 多次分析對比發(fā)現(xiàn) MAT 分析速度明顯優(yōu)于 Shark床三,另外針對 MAT 分析后仍持有統(tǒng)治者樹占用內(nèi)存我們也做了主動釋放锭碳,對比性能收益后采用基于 MAT 庫的分析引擎進行分析,對內(nèi)存泄漏引用鏈路自動歸并勿璃、大對象小對象引用鏈自動分析擒抛、大圖線下自動還原線上過濾無用鏈路,分析結(jié)果如下:

內(nèi)存泄漏
圖片

圖 7. 內(nèi)存泄漏鏈路

對泄漏的 Activity 的引用鏈進行了聚合分析补疑,方便一次性解決該 Activity 的泄漏鏈釋放內(nèi)存歧沪。

大對象
圖片

圖 8. 大對象鏈路

大對象不止分析了引用鏈路,還遞歸分析了內(nèi)部 top 持有對象(InRefrenrece)的 RetainedSize莲组。

小對象
圖片

圖 9. 小對象鏈路

小對象我們對 top 的外部持有對象(OutRefrenrece)進行聚合得到占有小對象最多的鏈路诊胞。

圖片
圖片

圖 10. 圖片鏈路

圖片我們過濾了圖片庫等無效引用且對 Android 8.0 以下的大圖在線下進行了還原。

圖片
回傳分析

為了最大限度的節(jié)省用戶流量且規(guī)避隱私風(fēng)險锹杈,我們通過自研 HPROF 裁剪工具 Tailor 在 dump 過程對 HPROF 進行了裁剪撵孤。

裁剪過程
圖片

圖 11. Tailor 裁剪流程

去除了無用信息

  • 跳過 header

  • 分 tag 裁剪

  • 裁剪無用信息:char[]; byte[]; timestamp; stack trace serial number; class serial number;

  • 壓縮數(shù)據(jù)信息

同時對數(shù)據(jù)進行 zlib 壓縮,在 server 端數(shù)據(jù)還原竭望,整體裁剪效果:180M--->50M---->13M

優(yōu)化實踐

內(nèi)存泄漏

除了通過后臺根據(jù) GCROOT+ 引用鏈自動分配研發(fā)跟進解決我們常見的內(nèi)存泄漏外邪码,我們還對系統(tǒng)導(dǎo)致一些內(nèi)存泄漏進行了分析和修復(fù)。

系統(tǒng)異步 UI 泄漏

根據(jù)上傳聚合的引用鏈我們發(fā)現(xiàn)在 Android 6.0 以下有一個 HandlerThread 作為 GCROOT 持有大量 Activity 導(dǎo)致內(nèi)存泄漏咬清,根據(jù)引用發(fā)現(xiàn)這些泄漏的 Activity 都被一個 Runnable(這里是 Runnable 是一個系統(tǒng)事件 SendViewStateChangedAccessibilityEvent)持有闭专,這些 Runnable 被添加到一個 RunQueuel 中,這個隊列本身被 TheadLocal 持有旧烧。

圖片

圖 12. HandlerThread 泄露鏈路

我們從 SendViewStateChangedAccessibilityEvent 入手對源碼進行了分析發(fā)現(xiàn)它在 notifyViewAccessibilityStateChangedIfNeeded 中被拋出影钉,系統(tǒng)的大量 view 都會在自身的一些 UI 方法(eg: setChecked)中觸發(fā)該函數(shù)。

圖片

SendViewStateChangedAccessibilityEventrunOrPost 方法會走到我們常用的 View 的 postDelay 方法中掘剪,這個方法在當(dāng) view 還未被 attched 到根 view 的時候會加入到一個 runQueue 中平委。

圖片

這個 runQueue 會在主線程下一次的 performTraversals() 中消費掉。

圖片

如果這個 runQueue 不在主線程那就沒有消費的機會夺谁。

根據(jù)上面的分析發(fā)現(xiàn)造成這種內(nèi)存泄漏需要滿足一些條件:

  1. view 調(diào)用了 postDelay 方法 (這里是 notifyViewAccessisbilityStateChangeIfNeeded 觸發(fā))

  2. view 處于 detached 狀態(tài)

  3. 上述過程是在非主線程里面操作的廉赔,ThreadLocal 非 UIThread愚墓,持有的 runQueue 不會走 performTraversals 消費掉。

抖音這邊大量使用了異步 UI 框架來優(yōu)化渲染性能昂勉,框架內(nèi)部由一個 HandlerThread 驅(qū)動浪册,完全符合上述條件。針對該問題岗照,我們通過反射獲取非主線程的 ThreadLocal村象,在每次異步渲染完主動清理內(nèi)部的 RunQueue。

圖片

圖 13. 反射清理流程

另外攒至,Google 在 6.0 上也修復(fù)了 notifyViewAccessisbilityStateChangeIfNeeded 的判斷不嚴謹問題厚者。

圖片

內(nèi)存泄漏兜底

大量的內(nèi)存泄漏,如果我們都靠推進研發(fā)解決迫吐,經(jīng)常會出現(xiàn)生產(chǎn)大于消費的情況库菲,針對這些未被消費的內(nèi)存泄漏我們在客戶端做了監(jiān)控和止損,將 onDestory 的 Activity 添加到 WeakRerefrence 中志膀,延遲 60s 監(jiān)控是否回收熙宇,未回收則主動釋放泄漏的 Activity 持有的 ViewTree 的背景圖和 ImageView 圖片。

大對象

主要對三種類型的大對象進行優(yōu)化

  • 全局緩存:針對全局緩存我們按需釋放和降級了不需要的緩存溉浙,盡量使用弱引用代替強引用關(guān)系烫止,比如針對頻繁泄漏的 EventBus 我們將內(nèi)部的訂閱者關(guān)系改為弱引用解決了大量的 EventBus 泄漏。

  • 系統(tǒng)大對象:系統(tǒng)大對象如 PreloadDrawable戳稽、JarFile 我們通過源碼分析確定主動釋放并不干擾原有邏輯馆蠕,在啟動完成或在內(nèi)存觸頂時主動反射釋放。

  • 動畫:用原生動畫代替了內(nèi)存占用較大的幀動畫惊奇,并對 Lottie 動畫泄漏做了手動釋放互躬。

圖片

圖 14. 大對象優(yōu)化點

小對象

小對象優(yōu)化我們集中在字段優(yōu)化、業(yè)務(wù)優(yōu)化颂郎、緩存優(yōu)化三個緯度吼渡,不同的緯度有不同的優(yōu)化策略。

圖片

圖 15. 小對象優(yōu)化思路

通用類優(yōu)化

在抖音的業(yè)務(wù)中祖秒,視頻是最核心且通用的 Model诞吱,抖音業(yè)務(wù)層的數(shù)據(jù)存儲分散在各個業(yè)務(wù)維護了各自視頻的 Model,Model 本身由于聚合了各個業(yè)務(wù)需要的屬性很多導(dǎo)致單個實例內(nèi)存占用就不低竭缝,隨著用戶使用過程實例增長內(nèi)存占用越來越大。對 Model 本身我們可以從屬性優(yōu)化和拆分這兩種思路來優(yōu)化沼瘫。

  • 字段優(yōu)化:針對一次性的屬性字段抬纸,在使用完之后及時清理掉緩存,比如在視頻 Model 內(nèi)部存在一個 Json 對象耿戚,在反序列完成之后 Json 對象就沒有使用價值了湿故,可以及時清理阿趁。
  • 類拆分:針對通用 Model 冗雜過多的業(yè)務(wù)屬性,嘗試對 Model 本身進行治理坛猪,將各個業(yè)務(wù)線需要用到的屬性進行梳理脖阵,將 Model 拆分成多個業(yè)務(wù) Model 和一個通用 Model,采用組合的方式讓各個業(yè)務(wù)線最小化依賴自己的業(yè)務(wù) Model墅茉,減少大雜燴 Model 不必要的內(nèi)存浪費命黔。

業(yè)務(wù)優(yōu)化

  • 按需加載:抖音這邊 IM 會全局保存會話,App 啟動時會一次性 Load 所有會話就斤,當(dāng)用戶的會話過多時相應(yīng)全局占用的內(nèi)存就會較大悍募,為了解決該問題,會話列表分兩次加載洋机,首次只加載一定數(shù)量到內(nèi)存坠宴,需要時再加載全部。

  • 內(nèi)存緩存限制或清理:首頁推薦列表的每一次 Loadmore 操作绷旗,都不會清理之前緩存起來的視頻對象喜鼓,導(dǎo)致用戶長時間停留在推薦 Feed 時,緩存起來的視頻對象過多會導(dǎo)致內(nèi)存方面的壓力衔肢。在通過實驗驗證不會對業(yè)務(wù)產(chǎn)生負面影響情況下對首頁的緩存進行了一定數(shù)量的限制來減小內(nèi)存壓力颠通。

緩存優(yōu)化

上面提到的視頻 Model,抖音最早使用 Manager 來管理通用的視頻實例膀懈。Manager 使用 HashMap 存儲了所有的視頻對象顿锰,最初的方案里面沒有對內(nèi)存大小進行限制且沒有清除邏輯,隨著使用時間的增加而不斷膨脹启搂,最終出現(xiàn) OOM 異常硼控。為了解決視頻 Model 無限膨脹的問題設(shè)計了一套緩存框架主要流程如下:

圖片

圖 16. 視頻緩存框架

使用 LRU 緩存機制來緩存視頻對象。在內(nèi)存中緩存最近使用的 100 個視頻對象胳赌,當(dāng)視頻對象從內(nèi)存緩存中移除時牢撼,將其緩存至磁盤中。在獲取視頻對象時疑苫,首先從內(nèi)存中獲取熏版,若內(nèi)存中沒有緩存該對象,則從磁盤緩存中獲取捍掺。在退出 App 時撼短,清除 Manager 的磁盤緩存,避免磁盤空間占用不斷增長挺勿。

圖片

關(guān)于圖片優(yōu)化曲横,我們主要從圖片庫的管理和圖片本身優(yōu)化兩個方面思考。同時對不合理的圖片使用也做了兜底和監(jiān)控。

圖片庫

針對應(yīng)用內(nèi)圖片的使用狀況對圖片庫設(shè)置了合理的緩存禾嫉,同時在應(yīng)用 or 系統(tǒng)內(nèi)存吃緊的情況下主動釋放圖片緩存灾杰。

圖片自身優(yōu)化

我們知道圖片內(nèi)存大小公式 = 圖片分辨率 * 每個像素點的大小

圖片分辨率我們通過設(shè)置合理的采樣來減少不必要的像素浪費熙参。

//開啟采樣ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)    .setDownsampleEnabled(true)    .build();Fresco.initialize(context, config);//請求圖片時艳吠,傳入resize的大小,一般直接取View的寬高ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)    .setResizeOptions(new ResizeOptions(50, 50))    .build();mSimpleDraweeView.setController(    Fresco.newDraweeControllerBuilder()        .setOldController(mSimpleDraweeView.getController())        .setImageRequest(request)        .build());

而單個像素大小孽椰,我們通過替換系統(tǒng) drawable 默認色彩通道昭娩,將部分沒有透明通道的圖片格式由 ARGB_8888 替換為 RGB565,在圖片質(zhì)量上的損失幾乎肉眼不可見弄屡,而在內(nèi)存上可以直接節(jié)省一半题禀。

圖片兜底

針對因 activity、fragment 泄漏導(dǎo)致的圖片泄漏膀捷,我們在 onDetachedFromWindow 時機進行了監(jiān)控和兜底迈嘹,具體流程如下:

圖片

圖 17. 圖片兜底流程

圖片監(jiān)控

關(guān)于對不合理的大圖 or 圖片使用我們在字節(jié)碼層面進行了攔截和監(jiān)控,在原生 Bitmap or 圖片庫創(chuàng)建時機記錄圖片信息全庸,對不合理的大圖進行上報秀仲;另外在 ImageView 的設(shè)置過程中針對 Bitmap 遠超過 view 本身超過大小的場景也進行了記錄和上報。

圖片

圖 18. 圖片字節(jié)碼監(jiān)控方案

更多思考

是不是解決了 OOM 內(nèi)存問題就告一段落了呢壶笼?作為一只追求極致的團隊神僵,我們除了解決靜態(tài)的內(nèi)存占用外也自研了 Kenzo(Memory Insight)工具嘗試解決動態(tài)內(nèi)存分配造成的 GC 卡頓。

Kenzo 原理

Kenzo 采用 JVMTI 完成對內(nèi)存監(jiān)控工作覆劈,JVMTI(JVM Tool Interface)是 Java 虛擬機所提供的 native 編程接口保礼。JVMTI 開發(fā)時,應(yīng)用建立一個 Agent 使用 JVMTI责语,可以使用 JVMTI 函數(shù)炮障,設(shè)置回調(diào)函數(shù),并從 Java 虛擬機中得到當(dāng)前的運行態(tài)信息坤候,并作出自己的業(yè)務(wù)判斷胁赢。

圖片

圖 19. Agent 時序圖

Jvmti SetEventCallbacks 方法可以設(shè)置目標虛擬機內(nèi)部事件回調(diào),可以根據(jù) jvmtiCapabilities 支持的能力和我們關(guān)注的事件來定義需要 hook 的事件白筹。

Kenzo 采用 Jvmti 完成如下事件回調(diào):

  • 類加載準備事件 -> 監(jiān)控類加載

  • ClassPrepare:某個類的準備階段完成智末。

  • GC -> 監(jiān)控 GC 事件與時間

  • GarbageCollectionStart:GC 啟動時。

  • GarbageCollectionFinish:GC 結(jié)束后徒河。

  • 對象事件 -> 監(jiān)控內(nèi)存分配

  • ObjectFree:GC 釋放一個對象時系馆。

  • VMObjectAlloc:虛擬機分配一個對象的時候。

框架設(shè)計

Kenzo 整體分為兩個部分:

生產(chǎn)端

  • 采集內(nèi)存數(shù)據(jù)

  • 以 sdk 形式集成到宿主 App

消費端

  • 處理生產(chǎn)端的數(shù)據(jù)

  • 輸入 Kenzo 監(jiān)控的內(nèi)存數(shù)據(jù)

  • 輸出可視化報表

圖片

圖 20. kenzo 框架

生產(chǎn)端主要以 Java 進行 API 調(diào)用虚青,C++完成底層檢測邏輯它呀,通過 JNI 完成底層邏輯控制。

消費端主要以 Python 完成數(shù)據(jù)的解析棒厘、視圖合成纵穿,以 HTML 完成頁面內(nèi)容展示。

工作流

圖片

圖 21. kenzo 框架

可視化展示

圖片

圖 22. kenzo 聚合展示

啟動階段內(nèi)存歸因

基于動態(tài)內(nèi)存監(jiān)控我們對最為核心的啟動場景的內(nèi)存分配進行了歸因分析奢人,優(yōu)化了一些頭部的內(nèi)存節(jié)點分配:

圖片

圖 23.啟動階段內(nèi)存節(jié)點歸因

另外我們也發(fā)現(xiàn)啟動階段存在大量的字符串拼接操作谓媒,雖然編譯器已經(jīng)優(yōu)化成了 StringBuider append,但是深入 StringBuider 源碼分析仍在存在大量的動態(tài)擴容動作(System.copy)何乎,為了優(yōu)化高頻場景觸發(fā)動態(tài)擴容的性能損耗句惯,在 StringBuilder 在 append的時候,不直接往 char[]里塞東西支救,而是先拿一個 String[]把它們都存起來抢野,到了最后才把所有 String 的 length 加起來,構(gòu)造一個合理長度的 StringBuilder各墨。通過使用編譯時字節(jié)碼替換的方式指孤,替換所有 StringBuilder 的 append 方法使用自定義實現(xiàn),優(yōu)化后首次安裝首頁 Feed 滑動 1min 的 FPS 提升 1 幀/S贬堵,非首次安裝啟動恃轩,滑動 1min 的 FPS 提升 0.6 幀/S。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末黎做,一起剝皮案震驚了整個濱河市叉跛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒸殿,老刑警劉巖筷厘,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宏所,居然都是意外死亡酥艳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門楣铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玖雁,“玉大人,你說我怎么就攤上這事盖腕『斩” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵溃列,是天一觀的道長劲厌。 經(jīng)常有香客問我,道長听隐,這世上最難降的妖魔是什么补鼻? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上风范,老公的妹妹穿的比我還像新娘咨跌。我一直安慰自己,他們只是感情好硼婿,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布锌半。 她就那樣靜靜地躺著,像睡著了一般寇漫。 火紅的嫁衣襯著肌膚如雪刊殉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天州胳,我揣著相機與錄音记焊,去河邊找鬼。 笑死栓撞,一個胖子當(dāng)著我的面吹牛遍膜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腐缤,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼捌归,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了岭粤?” 一聲冷哼從身側(cè)響起惜索,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剃浇,沒想到半個月后巾兆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡虎囚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年角塑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淘讥。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡圃伶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒲列,到底是詐尸還是另有隱情窒朋,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布蝗岖,位于F島的核電站鬼悠,受9級特大地震影響密浑,放射性物質(zhì)發(fā)生泄漏互例。R本人自食惡果不足惜绑雄,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一唧取、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧划提,春花似錦枫弟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽驼仪。三九已至掸犬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绪爸,已是汗流浹背湾碎。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奠货,地道東北人介褥。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像递惋,于是被迫代替她去往敵國和親柔滔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容