【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

騰訊開源發表於2018-01-13

元件介紹

OOMDetector是手Q自研的IOS記憶體監控元件,騰訊內部目前已有多個App接入了OOMDetector,它主要有以下兩個功能:

  • 爆記憶體堆疊統計:負責記錄程式記憶體分配堆疊和記憶體塊大小,在爆記憶體時Dump堆疊資料到磁碟

  • 記憶體洩漏檢測:檢測記憶體洩漏,目前支援Malloc記憶體塊和OC物件的洩漏檢測

OOMDetector可以快速幫助開發者發現和定位App爆記憶體問題和記憶體洩漏,元件目前已經在Github開源,原始碼地址:https://github.com/Tencent/OOMDetector。

背景

目前業內已有一些比較的IOS記憶體分析工具,下面逐個介紹這些工具的功能以及它們在使用上的不足。

Allocation

作為IOS開發,我們都很熟悉蘋果官方提供的Allocation記憶體分析工具,在開發除錯階段,可以用Allocation詳細分析App各模組記憶體佔用。Allocation對App的記憶體監控比較全面,能監控到所有堆記憶體以及部分VM記憶體分配。雖然Allocation的功能比較強大,但是它也有比較明顯的使用侷限性,主要表現為以下兩點:

  • 無法獨立在App執行,只能在除錯階段連線Mac使用

  • 效能較差,大型App開啟後容易引發卡死

這兩點限制決定了Allocation只適合於在開發階段輔助分析程式碼中存在的記憶體問題,而無法直接對線上使用者的問題進行監控和定位。

FBAllocationTracker

FBAllocationTracker是Facebook開源的記憶體分析工具,它的原理是用 Method Swizzling替換原本的alloc方法,這樣可以在App執行時記錄所有OC例項的分配資訊,幫助App在執行階段發現一些OC物件的異常增長問題。相比Allocation,FBAllocationTracker對App效能影響較低,可以在App中獨立執行。但是這個工具也有比較明顯的缺陷:

  • 監控範圍不夠全面,只能監控OC物件,不能監控C++物件和malloc記憶體塊以及VM記憶體

  • 沒有記憶體物件分配的堆疊資訊,對於開發者來說很難只通過物件的型別和數量定位到記憶體增長的原因

綜上所述,FBAllocationTracker雖然能獨立在App中執行,但是監控的記憶體範圍太小,同時記錄的物件資訊也過於簡單,對於分析記憶體問題幫助十分有限。

記憶體問題一直是手Q的關注重點,為了保證線上大盤使用者的記憶體質量,我們希望有一款工具能夠幫助監控和定位線上使用者的記憶體問題。基於這樣的背景,我們團隊自研了OOMDetector元件。OOMDetector通過Hook系統底層的記憶體分配方法,能夠記錄到程式所有記憶體分配的堆疊資訊,同時元件能夠在對效能流暢度影響不大的情況下能夠保證在App中獨立執行,可以方便用於分析和監控線上使用者的記憶體問題(爆記憶體或者記憶體洩漏問題)。

元件原理

爆記憶體堆疊統計

爆記憶體堆疊監控原理

爆記憶體堆疊監控的實現原理如圖1所示,通過Hook IOS系統底層記憶體分配的相關方法(包括malloc_zone相關的堆記憶體分配以及vm_allocate對應的VM記憶體分配方法),跟蹤並記錄程式中每個物件記憶體的分配資訊,包括分配堆疊、累計分配次數、累計分配記憶體等,這些資訊也會被快取到程式記憶體中。在記憶體觸頂的時候,元件會定時Dump這些堆疊資訊到本地磁碟,這樣如果程式爆記憶體了,就可以將爆記憶體前Dump的堆疊資料上報到後臺伺服器進行分析。

【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

圖1 爆記憶體監控原理

效能挑戰

App的記憶體分配方法的呼叫頻率非常高,在大型App中可能高達10W/次每秒。要Hook這類方法對元件的效能來說是極大的挑戰,因為如果元件本身耗時的話就很容易導致App卡頓甚至卡死。在OOMDetector中,我們對Hook方法程式碼的執行效率進行了嚴格控制,也採取了一些策略對Hook方法中耗時較多的堆疊回溯和鎖等待進行了優化:

  • 優化堆疊回溯方法

對於堆疊回溯,系統提供了backtrace_symbols方法可以直接獲取堆疊資訊,但是這個方法特別耗時。所以我們根據堆疊的回溯原理實現了更高效的堆疊回溯方法,優化後的方法在執行時只會獲取堆疊函式的地址資訊,在回寫磁碟的時候再根據動態庫的地址範圍拼裝成如圖2所示堆疊格式(類似Crash堆疊),後臺伺服器利用atos命令和符號表檔案就可以還原出對應的堆疊內容。通過這種方式可以把耗時較高的符號還原工作放到伺服器端,客戶端只需要執行耗時較少的堆疊函式地址回溯操作,優化後的堆疊回溯方法耗時低於1us。

【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

圖2 堆疊格式

  • 優化鎖等待耗時

對於多執行緒的記憶體分配,為了保證執行緒安全,堆疊資料的插入操作必須要上鎖。對於這種高頻呼叫的方法,鎖的效能是我們最關心的指標。IOS開發中NSLock和@synchronized是比較常用的,那麼這兩種鎖的效能如何呢?

我們通過測試程式碼對IOS中常用的鎖進行了測試,總結了圖2所示的各種鎖的效能比較圖,根據圖3的測試結果,NSLock和@synchronized的效能要低於pthread_mutex,效能最好的是自旋鎖OSSpinLock。

自旋鎖的原理是,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈等待鎖的釋放。相比互斥鎖而言,自旋鎖不會引起呼叫者休眠,節省了執行緒休眠的狀態切換,所以有更高的效率,但代價是增加了cpu的使用率。對於我們的場景,因為需要上鎖部分的程式碼執行耗時較少,採用OSSpinLock的自旋鎖並不會顯著增加cpu的使用率,所以我們優先考慮鎖的效率採用了OSSpinLock的方案。

【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

圖3 各種鎖的效能比較

堆疊聚類和壓縮

之前提到,我們的Hook方法會快取每個記憶體分配的堆疊資料。假設App的記憶體塊個數為25W,堆疊平均深度20行,每個堆疊地址採用8位元組的整型資料儲存,那麼25W個堆疊資料將佔用40M的記憶體空間。顯然這樣的記憶體增長對於任何App都是不可承受的,所以我們需要對元件的記憶體佔用進行優化。

我們分析爆記憶體問題時候,只需要分析那些記憶體佔用較大的堆疊,基本不用關心那些記憶體佔用較小的堆疊。所以我們的優化思路也很明確:只保留記憶體佔用較大的堆疊。要完成這個工作就必須對記憶體中所有堆疊先進行聚類合併,統計出每個堆疊累計的記憶體值。

具體的優化策略如圖4所示,對於每個記錄到的分配堆疊,首先通過md5演算法將堆疊資料壓縮為16位元組的md5,通過md5值進行聚類,快取中只保留16位元組的md5資料,只有當某個堆疊的累計記憶體超過一定閥值時,才會保留原始堆疊資訊,這樣因為超過閥值的堆疊數量有限,堆疊原始資訊佔用的空間幾乎就可以忽略不計了。

【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

圖4 堆疊聚類和壓縮原理

採用兩種方式可以將堆疊降低到優化前的1/40左右,優化後的元件記憶體基本不會對App的記憶體造成太大影響。

資料Dump方案

前面提到,在記憶體觸頂後要將記憶體中的堆疊資料定時Dump到磁碟中,常規的方案是IO介面直接把資料寫入到磁碟。因為資料Dump的頻率較高,頻繁的IO操作會導致程式卡頓。因為資料Dump的操作是非常高頻的,所以我們採用了效率更高的mmap方式。

mmap是一種記憶體對映檔案的方法,即將一個檔案或者其它物件對映到程式的地址空間。實現這樣的直接對映關係後,寫檔案的過程程式不會有額外的檔案的資料拷貝操作,避免了核心空間和使用者空間的頻繁切換,如圖5所示。根據我們的程式碼實測,向mmap對映空間寫資料的效能與直接寫記憶體一致,效率遠高於IO操作。

【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

圖5 記憶體對映原理

那麼mmap的回寫時機是怎樣的?根據官方文件描述,主要有如下時機:

  • 系統記憶體不足時

  • 程式crash時

  • 主動呼叫 msync時

mmap 在記憶體不足時會主動進行回寫操作,這樣的機制也保證我們的監控元件能在程式爆記憶體前將快取中的資料回寫到磁碟,從這一點看採用mmap的方式相比常規IO操作也有更強可靠性。

記憶體洩漏檢測

除了爆記憶體堆疊監控,OOMDetector還整合了記憶體洩漏檢測功能,能夠檢測Malloc記憶體塊和OC物件的“無主記憶體洩漏”。所謂“無主記憶體洩漏”是指記憶體塊在程式內已經沒有引用卻無法正常釋放的記憶體塊。

按照之前介紹的方案,OOMDetector可以記錄到每一個物件的分配堆疊資訊,要從這些物件中找出 “洩漏物件”,我們需要知道在程式可訪問的程式記憶體空間中,是否有“指標變數”指向對應的記憶體塊,那些在整個程式記憶體空間都沒有指標指向的記憶體塊,就是我們要找的洩漏記憶體塊。如圖2所示,在IOS系統中,可能包含指標變數的記憶體區域有堆記憶體、棧記憶體、全域性資料區和暫存器,OOMDetector 通過對這些區域遍歷掃描即可找到所有可能的“指標變數”,整個掃描流程結束後都沒有“指標變數”指向的記憶體塊即是洩漏記憶體塊。

為了避免記憶體訪問衝突,掃描過程需要掛起所有執行緒,整個過程會卡住程式1-2秒。因為掃描過程較為耗時,這個功能目前主要用於App的測試階段,與自動化測試結合可快速高效的發現洩漏問題。

【騰訊開源】iOS爆記憶體問題解決方案-OOMDetector元件

圖6 記憶體洩漏檢測原理

展望

開源只是開始,我們後續仍會不斷對OOMDetector元件進行改進,也歡迎大家對元件多提意見。如果你的IOS應用也在受到記憶體問題困擾或者你也對IOS記憶體監控技術感興趣,那麼來了解下我們的元件吧!

相關文章