應用開發(fā)優(yōu)化方面今妄,內存占用是一個不可避免的話題郑口。在開發(fā)過程中,內存泄漏的檢測盾鳞,一直是工作中不可避免的任務犬性。
當我們分配了一塊內存,并且設置對象腾仅。如果使用完成后忘記釋放乒裆,就會發(fā)生內存泄漏,當然ARC之后推励,蘋果已經自動幫做了很多內存的釋放工作鹤耍,但是如果開發(fā)過程不注意,有循環(huán)引用的話肉迫,還是會發(fā)生內存泄漏,當發(fā)生內存泄漏時惰蜜,我們需要盡快找到并修復它們昂拂。
Xcode提供了Instruments工具來幫我們進行內存泄漏的檢測受神,以下是步驟
- 打開Xcode抛猖,profiling編譯。
- 載入Instruments鼻听。
- 使用應用程序财著,嘗試盡可能多的重現(xiàn)場景和行為。
- 查看內存泄漏的根源撑碴。
- 定位后進行修復撑教。
開發(fā)時,每次進行profile是很耗費時間的醉拓,同時重復大量的手動操作伟姐。
如果有一套自動化的工具,能夠更快更方便的找到內存泄漏的話亿卤,開發(fā)效率也會大大提升愤兵。接下來就引入到了FB發(fā)布的三個工具:FBRetainCycleDetector、FBAlloctionTracker排吴、FBMemoryProfiler.
循環(huán)引用 (Retain cycles)
Objective-C是使用引用計數(shù)來管理內存和釋放不使用的對象秆乳。內存中任何一個對象都可以持有(retain)其他的對象,只要前面的對象需要它钻哩,對象就會一直保持在內存中屹堰,大部分時間內,這都工作的很好街氢,但是當兩個對象互相持有的時候扯键,這就會陷入一個僵局,直接或者通過間接對象連接它們珊肃。這種持有引用的環(huán)荣刑,我們稱之為循環(huán)引用(Retain cycles).
循環(huán)引用會導致很多問題,最好的情況下近范,對象只會在內存中占有一點點位置嘶摊,如果這個被泄露的對象還在進行一些工作的話,引用程序的其他部分可以使用的內存就會變少评矩;最壞的情況下叶堆,如果泄漏導致使用超出可用內存的容量,那么斥杜,應用程序就會崩潰虱颗。
在手動性能分析期間沥匈,往往有會出現(xiàn)一些循環(huán)引用,很容易引起內存泄漏忘渔,但是進行定位的話高帖,比較麻煩。尤其項目比較龐大且業(yè)務關聯(lián)較多的時候畦粮。而循環(huán)引用檢測器(FBRetainCycleDetector)可以很容易的找到他們散址。
在運行時檢測循環(huán)引用
在Objectice-C中找循環(huán)引用類似于在一個有向無環(huán)圖中找環(huán),對象就是圖的節(jié)點宣赔,邊就是對象之間的引用(如果對象A持有對象B预麸,那么,A到B之間就存在著引用)儒将。我們的Objective-C對象已經在圖中吏祸,我們要做的就是用深度優(yōu)先搜索遍歷它。以下是FB的示例:
這個有點抽象钩蚊,但是效果很好贡翘。我們必須確保我可以向節(jié)點一樣使用對象,對于每個對象砰逻,我們都可以獲取到它引用的所有對象鸣驱。這些引用可能是weak,也可能是strong诱渤。只有強引用才會導致循環(huán)引用丐巫。對于每個對象來說,我們需要知道該如何找出這些引用勺美。
幸運的是递胧,OC為我們提供了一套強有力的、內省的運行時庫赡茸。這讓我們在圖中可以有足夠的數(shù)據(jù)去挖掘可能出現(xiàn)的環(huán)缎脾。
圖中的節(jié)點可以是對象,同時block也是循環(huán)引用的高發(fā)地占卧,我們分別看一下遗菠。
對象
運行時有很多工具允許我們對對象進行內省。
我們要做的第一件事就是獲取對象的實例變量的布局(ivar layout).
const char *class_getIvarLayout(Class cls)
const char *class_getWeakIvarLayout(Class cls)
對于對象华蜒,實例變量的布局描述了我們在哪可以找到其他對象的引用辙纬。它提供了我們一個索引(index),這代表我們需要在對象地址上添加一個偏移量(offset),就可以得到它所引用的對象的地址叭喜。運行時也允許我們獲取"弱引用實例變量布局(weak ivar layout)"贺拣。
這也部分支持Objective-C++。在Objective-C++中,我們可以在結構體中定義對象譬涡,但是這不會在實例變量布局中獲取到闪幽。運行時提供了"類型編碼"來處理這個問題。對于每一個實例變量來說涡匀,類型編碼描述了變量是如何結構化的盯腌。如果這是一個結構體,它會描述它包含了哪些字段和類型陨瘩。我么你計算出它的偏移量腕够,在圖中,找到它們所指向的對象拾酝。稍后再看源碼是會具體分析如何找出
Block
Block和對象有一點不一樣燕少。運行時不會讓我們很輕易的看到它們的布局卡者,但是我們仍然可以進行猜測蒿囤。
我們可以使用ABI(application binary interface for blocks - 應用程序二進制Block接口)。它描述了Block在內存中的樣子崇决。如果我們知道我們在處理的的引用是一個Block材诽,我們可以把它丟在一個假的結構體中來模仿Block。在放到一個C語言的結構體之后恒傻,我們可以知道Block所持有的對象脸侥。然而,有一個尷尬的問題盈厘,不知道這些引用的強引用還是弱引用睁枕。
為了解決這個問題,F(xiàn)B使用了一個黑盒技術沸手。創(chuàng)建一個對象來假扮想要調查的Block外遇。因為我們知道Block 的接口,我們知道在哪可以找到Block持有的引用契吉。偽造的對象會擁有"釋放檢測(release detectors)"來代替這些引用跳仿。釋放檢測器是一些很小的對象,它們會觀察發(fā)送給它們的釋放消息捐晶。但持有者想要放棄它的持有的時候菲语,這些消息會發(fā)送給強引用對象,但我們釋放我們偽造的對象的時候惑灵,我們可以檢測哪些檢測器接收到了這些消息山上。只要知道那些索引在偽造的對象的檢測器中,我們就可以找到原來Block中實際持有的對象英支。
自動化
然而原理介紹完佩憾,真正提高檢測效率的是,它可以連續(xù)的自動的運行潭辈。
客戶端部分自動化是很簡單的鸯屿,我們可以在定時器上運行循環(huán)引用檢測器澈吨,定期掃描內存去尋找循環(huán)引用,雖然這不是完全沒有問題寄摆。但是運行的時候發(fā)現(xiàn)谅辣,掃描完整個內存空間比較慢。為了提高效率婶恼,我們可以在開始檢測的時候桑阶,提供一組候選對象。FBAllocationTracker這個工具可以主動跟蹤NSObject子類的創(chuàng)建和釋放勾邦。它可以以很小的性能開銷來獲取任何類的任何實例蚣录。
現(xiàn)在呢,我們只要在NSTimer上使用FBRetainCycleDetector眷篇,再用FBAllocationTracker來抓取實例配合跟蹤就行萎河。
然而,實際過程中還是會遇到問題蕉饼,循環(huán)引用可以包含任意數(shù)量的對象虐杯,一個錯誤引用會導致很多環(huán)的情況,處理起來比較復雜昧港。如圖
在環(huán)中擎椰,A-B是一個壞連接,創(chuàng)建了兩個環(huán):A-B-C-D和A-B-C-E创肥。
這種情況導致兩個問題:
- 我們不想給一個壞連接導致的兩個循環(huán)引用分別標記达舒。
- 我們不想給可能代表兩個問題的兩個循環(huán)引用一起標記,即使它們共享一個連接叹侄。
所以我們需要給循環(huán)引用定義簇組(clusters),鑒于這樣的情況巩搏,F(xiàn)B寫了一個算法來找到這些問題。 - 在給定的時間收集所有的環(huán)
- 對于每一個環(huán)圈膏,提取Facebook特定的類名塔猾。
- 對于每一個環(huán),找到包含在環(huán)內的被報告的最小的環(huán)稽坤。
- 依據(jù)上面的最小環(huán)丈甸,將環(huán)添加到組中。
-
只報告最小環(huán)尿褪。
最后一部分當然是找出導致循環(huán)引用的代碼是誰寫的睦擂,進行一番批評教育??.
這個系統(tǒng)如下:
手動性能分析
雖然自動化有助于簡化發(fā)現(xiàn)循環(huán)引用的過程,降低人員的消耗杖玲,手動性能分析仍然有它的用武之地顿仇。FB創(chuàng)建的另一個工具允許任何人查看內存使用,即實時可視化。
FBMemoryProfiler可以很容易的添加到任何應用程序中臼闻,可以讓你手動配置構建文件鸿吆,可以讓你在引用程序內運行循環(huán)應用檢測。當然它是借用FBAllocationTracker和FBRetainCycleDetector來實現(xiàn)此功能述呐。
生成(Generations)
FBMemoryProfiler的一個很偉大的特性就是"生成追蹤(generation tracking)"惩淳,類似于蘋果的Instruments的生成追蹤。生成只是簡單的在兩次標記之間拍攝所有仍然存貨的對象的快照乓搬。
使用FBMemoryProfiler的界面思犁,我們可以標記生成,例如,分配三個對象进肯。然后我們標記另一個生成激蹲,之后繼續(xù)分配對象。第一個生成包含我們一開始的三個對象江掩。如果任意一個對象釋放了学辱,它會從我們第二個生成中移除。
當我們有一個重復的任務频敛,我們認為可能會內存泄漏的時候项郊,生成追蹤是很有用的,例如斟赚,導航View Controller的進出。在每次開始我們的任務的時候差油,我們標記一個生成拗军,然后對之后的每個生成進行調查。如果一個對象不應該存活這么長時間蓄喇,我們可以在FBMemoryProfiler界面清楚的看到发侵。
綜上所述,無論開發(fā)的應用程序是大是小妆偏,功能是多是少刃鳄,好的工程師都應該有好的內存管理。在這些工具的幫助下钱骂,我們可以更簡單的找到并修復這些內存泄漏叔锐,可以有更多的時間去做更有意義的事,like編寫更好的代碼见秽。