作者 | 王海超
背景介紹
直播 OOM 問(wèn)題比較棘手難以定位,主要體現(xiàn)在涉及的業(yè)務(wù)很多,從定位到解決花費(fèi)時(shí)間比較久。為了提前觸達(dá)問(wèn)題,提高定位的效率,也是對(duì)現(xiàn)有工具的補(bǔ)充,提出直播內(nèi)存抖動(dòng)解決方案- MemoryThrashing。
為什么要提出這個(gè)方案 ?
- 現(xiàn)有的 “MemoryGraph” 工具可以通過(guò)抓取的“MemoryGraph” 文件分析 OOM 成因,比如內(nèi)存泄漏、內(nèi)存占用過(guò)高導(dǎo)致的 OOM 問(wèn)題,但因?yàn)樾阅荛_(kāi)銷很大,所以是采樣上報(bào)且采樣率很低,不容易觸達(dá)問(wèn)題, 只能定向?qū)σ阎脩糸_(kāi)啟才行。期望自研一個(gè)工具,在內(nèi)存增長(zhǎng)時(shí)可以發(fā)現(xiàn)問(wèn)題,也能用于 OOM 發(fā)生后的分析,同時(shí)具備性能開(kāi)銷小、全采樣的能力;
- “MemoryGraph” 生成時(shí)可能不是內(nèi)存高位,比如設(shè)備內(nèi)存是 4G,生成的“MemoryGraph” 可能是 1G,會(huì)影響 OOM 分析;
什么是 Thrashing(抖動(dòng)) ?
維基百科中 thrashing 定義:
- In computer science, thrashing occurs when a computer's virtual memory resources are overused, leading to a constant state of paging and page faults, inhibiting most application-level processing.([1]) This causes the performance of the computer to degrade or collapse.
從業(yè)務(wù)視角定義內(nèi)存 thrashing:
通俗講就是性能數(shù)據(jù)有大的波動(dòng),拿內(nèi)存來(lái)講,當(dāng)內(nèi)存短時(shí)間從 600M 漲到 800M 叫作一個(gè)抖動(dòng)。希望通過(guò)自研工具找出這 200 M 內(nèi)存增長(zhǎng)來(lái)自于哪里,在實(shí)際的 OOM 案例中因內(nèi)存突增導(dǎo)致的 OOM 是比較常見(jiàn)的,具體現(xiàn)象如下:
- 內(nèi)存未回落:內(nèi)存突增一般發(fā)生在一兩分鐘內(nèi),內(nèi)存從 1G 漲到 3G,這部分內(nèi)存會(huì)一直滯留在內(nèi)存中不會(huì)被釋放掉或者沒(méi)有機(jī)會(huì)釋放掉直接 OOM,同時(shí)助高了內(nèi)存水位很容易發(fā)生 OOM 問(wèn)題;
- 內(nèi)存回落:內(nèi)存突增到一定水位開(kāi)始回落未形成 OOM,這種現(xiàn)象通常是內(nèi)存問(wèn)題不夠劣化,或者機(jī)器本身內(nèi)存足夠大不容易 OOM,雖然沒(méi)有造成 OOM 但也是一個(gè)潛在的問(wèn)題;
以臨時(shí)對(duì)象、內(nèi)存堆積為例來(lái)闡述如何定位該類問(wèn)題,通過(guò)“AllocTime Summary” 描述臨時(shí)對(duì)象分配次數(shù),通過(guò) “Memory Summary” 描述內(nèi)存堆積。
臨時(shí)對(duì)象
臨時(shí)對(duì)象:短時(shí)間分配大量對(duì)象,導(dǎo)致直播穩(wěn)定性波動(dòng)較大,可能使內(nèi)存、CPU 負(fù)載變高。這類問(wèn)題通常表現(xiàn)為短時(shí)間內(nèi)存沖高或者直接 OOM,或之后開(kāi)始迅速回落到正常水位,這類對(duì)象不會(huì)駐留內(nèi)存過(guò)久,通過(guò)監(jiān)控 “臨時(shí)對(duì)象” 可以提前發(fā)現(xiàn)這類問(wèn)題。
以上是按分配次數(shù)(AllocTime Summary)統(tǒng)計(jì)的 TOP 臨時(shí)對(duì)象,“AllocTime Summary 1” 代表第一次采樣 Class 的分配次數(shù)其它依次類推。舉例:通過(guò) diff “AllocTime Summary 2” 與 “AllocTime Summary 1” 差值可知 “LivexxxA” 在采樣周期分配了 7803 次,由于未采集到 “Memory Summary” 信息,可認(rèn)為未有內(nèi)存駐留。
內(nèi)存堆積
內(nèi)存堆積:內(nèi)存駐留了大量對(duì)象,而且這類對(duì)象短時(shí)間不會(huì)釋放掉,導(dǎo)致內(nèi)存水位居高不下,很容易觸發(fā) OOM 問(wèn)題。
以上是按內(nèi)存駐留統(tǒng)計(jì)的 TOP 實(shí)例,“Memory Summary 1” 代表第一次采樣實(shí)例數(shù)量的內(nèi)存駐留信息其他依次類推。舉例:通過(guò) diff “Memory Summary 2” 與 “Memory Summary 1” 可知 “LivexxxA” 在采樣周期內(nèi)增長(zhǎng)了 56791 個(gè),根據(jù)最后一次采樣可知內(nèi)存駐留了總共 69904 個(gè)實(shí)例,通過(guò)采樣可知“LivexxxA” 每次都是遞增的。
MemoryThrashing 方案
方案調(diào)研
方案思路是做內(nèi)存差值找出增長(zhǎng),通過(guò)采樣多個(gè)時(shí)刻的內(nèi)存信息(目前主要監(jiān)控 Class 的實(shí)例個(gè)數(shù)), Diff 出內(nèi)存信息找出 TOP 增長(zhǎng),達(dá)到歸因的目的。
- 內(nèi)存區(qū):通過(guò)內(nèi)存節(jié)點(diǎn)遍歷統(tǒng)計(jì) Class 實(shí)例個(gè)數(shù);
- Runtime:通過(guò) alloc、dealloc 計(jì)數(shù)實(shí)現(xiàn)統(tǒng)計(jì)實(shí)例存活數(shù)量;
內(nèi)存區(qū)
通過(guò)內(nèi)存節(jié)點(diǎn)遍歷與已注冊(cè)的 Class 比較統(tǒng)計(jì)實(shí)例個(gè)數(shù),該方案的優(yōu)點(diǎn)是可以監(jiān)控整個(gè) APP 的 OC 對(duì)象實(shí)例個(gè)數(shù),面對(duì)直播業(yè)務(wù)場(chǎng)景需不需監(jiān)控整個(gè) APP 的對(duì)象,目前看暫時(shí)用不到,需求出發(fā)點(diǎn)是監(jiān)控直播場(chǎng)景且滿足一定條件。比如:直播觀播一段時(shí)間后內(nèi)存的大幅波動(dòng),場(chǎng)景比較聚焦。另一個(gè)考慮是如果當(dāng)前內(nèi)存比較大,遍歷 zone 會(huì)比較耗時(shí),如果不掛起線程會(huì)有潛在的崩潰問(wèn)題、以及數(shù)據(jù)不準(zhǔn)問(wèn)題。
RunTime
通過(guò) Hook 的方式,統(tǒng)計(jì) Class 實(shí)例的分配、釋放次數(shù),達(dá)到記錄實(shí)例存活個(gè)數(shù)的目的,可監(jiān)控固定場(chǎng)景的 OC 實(shí)例增長(zhǎng)情況,如直播間內(nèi)的內(nèi)存突增,范圍比較小不需要統(tǒng)計(jì)過(guò)多的無(wú)用對(duì)象。該方案相對(duì)內(nèi)存區(qū)遍歷耗時(shí)小,且不會(huì)有野指針問(wèn)題。但需要注意的是監(jiān)控對(duì)象時(shí)對(duì)性能的影響,目前采用的是 RunTime 方案,從線下直播間測(cè)試情況看對(duì)主線程的影響忽略不計(jì)。
方案設(shè)計(jì)
在實(shí)際開(kāi)發(fā)過(guò)程中發(fā)現(xiàn)對(duì)象的創(chuàng)建、釋放處于復(fù)雜的多線程環(huán)境中,處理不當(dāng)會(huì)對(duì)業(yè)務(wù)產(chǎn)生潛在的影響,影響到業(yè)務(wù)執(zhí)行效率或者造成穩(wěn)定性問(wèn)題:
- 容器置于多線程下會(huì)有線程安全問(wèn)題;
- 過(guò)度的使用鎖會(huì)阻塞業(yè)務(wù)代碼執(zhí)行,也可能觸發(fā) Watchdog 機(jī)制導(dǎo)致 APP 被 kill;
經(jīng)過(guò)優(yōu)化采用多級(jí)緩存方案解決主線程的性能開(kāi)銷問(wèn)題,達(dá)到主線程幾乎零開(kāi)銷。
監(jiān)控流程
在進(jìn)入直播間一段時(shí)間后開(kāi)啟監(jiān)控,通過(guò)監(jiān)控內(nèi)存值變化來(lái)區(qū)分是否開(kāi)啟采樣功能,開(kāi)啟采樣后會(huì)進(jìn)入連續(xù)多次采樣階段,多次采樣完成后進(jìn)行數(shù)據(jù)上報(bào),上報(bào)完成后會(huì)繼續(xù)監(jiān)控內(nèi)存。
數(shù)據(jù)展示
在高熱直播間多次采樣的內(nèi)存快照,采集 TOP 100 數(shù)據(jù),以 “LivexxxA” 為例兩次采樣中第二次增長(zhǎng)了 4125 個(gè)實(shí)例,可以簡(jiǎn)單歸因 “LivexxxA” 相關(guān)業(yè)務(wù)導(dǎo)致 “MemoryThrashing”,可以從 “LivexxxA” 相關(guān)業(yè)務(wù)入手排查。
方案優(yōu)缺點(diǎn)
方案 | 優(yōu)點(diǎn) | 缺點(diǎn) |
“MemoryThrashing” | 可以多次采樣,對(duì)比內(nèi)存增長(zhǎng)趨勢(shì);性能開(kāi)銷小,可線上全量;提前感知內(nèi)存問(wèn)題;上手簡(jiǎn)單,通過(guò)對(duì)象數(shù)量就可以排查問(wèn)題; | 不支持多語(yǔ)言,只限于 oc 語(yǔ)言;不具備通過(guò)內(nèi)存節(jié)點(diǎn)關(guān)系分析內(nèi)存泄漏問(wèn)題,只能找出堆積的對(duì)象;不具備分析多個(gè)內(nèi)存區(qū)的能力;Hook 方式影響方法緩存; |
“MemoryGraph” | 問(wèn)題發(fā)現(xiàn)能力強(qiáng):可以通過(guò)內(nèi)存節(jié)點(diǎn)關(guān)系分析內(nèi)存泄漏導(dǎo)致的 OOM 問(wèn)題;可以統(tǒng)計(jì)內(nèi)存區(qū)的內(nèi)存占用情況;適用多語(yǔ)言;上手復(fù)雜,需要梳理內(nèi)存節(jié)點(diǎn)引用關(guān)系; | 線程掛起會(huì)影響業(yè)務(wù)執(zhí)行,用戶感知明顯;內(nèi)存使用越高,內(nèi)存區(qū)遍歷越耗時(shí);只能少量采樣; |
實(shí)踐案例
目前 “MemoryThrashing” 已經(jīng)部署了,可以監(jiān)控測(cè)試環(huán)境,后續(xù)將部署到線上。通過(guò)線下看提前暴露了很多問(wèn)題,相對(duì)以往方式只有問(wèn)題發(fā)生了或者產(chǎn)生了明顯影響才能感知到,需要 QA 反饋到 RD,通過(guò)“MemoryThrashing”大大提升了排查效率,很好的將劣化問(wèn)題前置發(fā)現(xiàn),以下抽取其中兩個(gè)案例。
內(nèi)存堆積
如下,多個(gè)采樣周期內(nèi)出現(xiàn)了大量對(duì)象的分配問(wèn)題,且這些對(duì)象未釋放,并且導(dǎo)致了內(nèi)存明顯上漲,采樣周期 3 比采樣周期 2 多分配了 234024 個(gè)對(duì)象,且最后內(nèi)存駐留了 238800 個(gè) “LivexxxBigDataRead” 對(duì)象,占用內(nèi)存 10.9M。
臨時(shí)對(duì)象
如下,是開(kāi)播場(chǎng)景抓到的問(wèn)題,在主播端開(kāi)啟彈幕狂歡時(shí),過(guò) Effect 認(rèn)出人臉后,就會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的輪廓模型給到中臺(tái)去畫輪廓,頻率會(huì)很高,每 5 秒周期(實(shí)際時(shí)間更小)臨時(shí)對(duì)象增量高峰可到 6w 個(gè)(后兩次采樣差值),由于未生成 “Memory Summary” 信息可認(rèn)為未駐留內(nèi)存 ,累計(jì)過(guò)百萬(wàn)次對(duì)象分配,對(duì)開(kāi)播性能會(huì)產(chǎn)生直接影響:
未來(lái)規(guī)劃
歸因能力
只統(tǒng)計(jì) OC 對(duì)象數(shù)據(jù)在某些情況下可能不夠,比如公共基礎(chǔ)對(duì)象異常增長(zhǎng),則沒(méi)有辦法追蹤到具體成因,如果帶有對(duì)象引用關(guān)系可以進(jìn)一步鎖定問(wèn)題。當(dāng)然這些都是對(duì) “Memory Graph” 能力的補(bǔ)充,如果“Memory Graph” 已經(jīng)抓到了數(shù)據(jù),可以結(jié)合“Memory Graph” 鎖定對(duì)象引用鏈路繼而找到業(yè)務(wù)。
- “MemoryThrashing” 可以加上對(duì)象引用關(guān)系計(jì)算,從效率上講沒(méi)必要對(duì)所有的對(duì)象查找其引用關(guān)系,查找引用關(guān)系是比較耗時(shí)的。只需查找 TOP 增長(zhǎng)點(diǎn)的關(guān)鍵對(duì)象引用關(guān)系,實(shí)測(cè)可能只需要查找?guī)讉€(gè)對(duì)象的引用關(guān)系。
- 通過(guò)線程堆棧采樣記錄信息;
CPU 監(jiān)控
根據(jù)以往案例如:OOM、ANR 有不少會(huì)伴隨著高 CPU 使用率,比如某次案例由大量數(shù)據(jù)處理導(dǎo)致的 OOM 問(wèn)題,經(jīng)排查發(fā)現(xiàn)負(fù)責(zé)該業(yè)務(wù)處理的線程 CPU 使用率很高,所以通過(guò)監(jiān)控線程 CPU 使用率,來(lái)補(bǔ)充監(jiān)控顯得很有必要,可以通過(guò)線程名字、堆棧, 鎖定懷疑的業(yè)務(wù)。