你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 分享:在iOS上自動檢測內存洩露

分享:在iOS上自動檢測內存洩露

編輯:IOS開發基礎

safe_image600.jpg

原文:Automatic memory leak detection on iOS,譯者:付軍(博客)

手機設備的內存是一個共享資源。應用程序可能會不當的耗盡內存、崩潰,或者遭遇大幅度的性能降低。

Facebook iOS客戶端有很多功能,並且它們共享同一塊內存空間。如果任何特定的功能消耗過多的內存,就會影響到整個應用程序。這是可能發生的,比如,這個功能導致了內存洩露。

當我們分配了一塊內存,並設置了對象之後,如果在使用完了之後忘記釋放,這就會發生內存洩露。這意味著系統是無法回收內存並交予他人使用,這也最終意味著我們的內存將會逐漸耗盡。

在Facebook,我們有很多工程師在代碼庫的不同部分上工作。這不可避免的會發生內存洩露。當發生內存洩露之後,我們需要盡快找到並修復它們。

一些工具已經可以找到內存洩露,但是它們需要大量的人工干預:

  • 打開Xcode,給性能分析(profiling)編譯。

  • 載入Instruments。

  • 使用應用程序,嘗試盡可能多的重現場景和行為。

  • 查看內存和洩露。

  • 追蹤內存洩露的根源。

  • 修復這個問題。

這意味著每次都需要重復大量的手動操作。為此,在我們的開發周期上,我們可能無法盡可能早的定位和修復內存洩露問題。

自動化可以在不需要更多開發者的情況下,更快的找到內存洩露。為了解決這個問題,我們做了一套工具來自動化的處理和修復我們代碼庫中的一些問題。今天,我們很興奮的發布這些工具:FBRetainCycleDetector、FBAllocationTracker、FBMemoryProfiler。

循環引用(Retain cycles)

Objective-C 使用引用計數去管理內存和釋放不使用的對象。內存中的任何一個對象都可以持有(retain)其他的對象,只要前面的對象需要它,對象就會一直保持在內存中。查看這個的一個方法是這個對象持有其他的對象。

在大部分時間內,這都工作的很好,但當兩個對象互相持有的時候,這就會陷入一個僵局。直接,或者更常見的,通過間接對象連接它們。這種持有引用的環我們叫做循環引用(Retain cycles)。

blogimage_memoryleak_1.jpg

循環引用會導致一些列的問題。最好的情況下,對象只會在內存中占有一點點位置。如果這個被洩露的對象正積極地做個一些不平凡的事情,應用程序的其他部分就只會有更少的內存。最壞的情況下,如果洩露導致使用超出可用內存的容量,那麼,應用程序會崩潰。

在手動性能分析期間,我們發現,我們往往有一些循環引用。我們很容易引起內存洩露,但是很難找到它們。循環引用檢測器可以很容易的找到它們。

在運行時檢測循環引用

在 Objective-C 中找循環引用類似於在一個有向無環圖(directed acyclic graph)中找環, 而節點就是對象,邊就是對象之間的引用(如果對象A持有對象B,那麼,A到B之間就存在著引用)。我們的 Objective-C 對象已經在我們的圖中,我們要做的就是用深度優先搜索遍歷它。

這有點抽象,但效果很好。我們必須確保我們可以像節點一樣使用對象,對於每個對象,我們都可以獲取到它引用的所有對象。這些引用可能是weak,也可能是strong。只有強引用才會導致循環引用。對於每個對象來說,我們需要知道如何找出這些引用。

幸運的是,Objective-C提供了一個強有力的、內省的運行時庫。這讓我們在圖中可以有足夠的數據去挖掘。

圖中的節點可以是對象,也可以是Block。讓我們來分別討論一下。

對象

運行時有很多工具允許我們對對象進行內省。

我們要做的第一件事是獲取對象的實例變量的布局(ivar layout)。

const char *class_getIvarLayout(Class cls);
const char *class_getWeakIvarLayout(Class cls);

對於對象,實例變量的布局描述了我們在哪兒可以找到其他對象的引用。它會提供給我們一個索引(index),這代表我們需要在對象地址上添加一個偏移量(offset),就可以得到它所引用的對象的地址。運行時也允許我們獲取“弱引用實例變量布局(weak ivar layout)”。

這也部分支持Objective-C++。在Objective-C++中,我們可以在結構體中定義對象,但是這不會在實例變量布局中獲取到。運行時提供了“類型編碼(type encoding)”來處理這個問題。對於每一個實例變量來說,類型編碼描述了變量是如何結構化的。如果這是一個結構體,它會描述它包含了哪些字段和類型。我們計算出它們的偏移量,在圖中,找出它們所指向的對象。

也有一些邊緣條件我們不會深入。大部分是一些不同的集合,我們不得不列舉它們去獲得它們持有的對象,這可能會導致一些副作用。

Block

Block和對象有一點不一樣。運行時不會讓我們很輕易的看到它們的布局,但是我們仍然可以猜測。

在處理Block的時候,我們可以使用 Mike Ash 在他的項目Circle(第一時間啟發FBRetainCycleDetector的項目)中提出的想法。

我們可以使用的是ABI(application binary interface for blocks - 應用程序二進制Block接口)。它描述了Block在內存中的樣子。如果我們知道我們在處理的引用是一個Block,我們可以把它丟在一個假的結構體中來模仿Block。在放到一個C語言的結構體之後,我們可以知道Block所持有的對象。不幸的是,我們不知道這些引用是強引用還是弱引用。

為了解決這個問題,我們使用了一個黑盒技術。我們創建一個對象來假扮我們想要調查的Block。因為我們知道Block的接口,我們知道在哪可以找到Block持有的引用。我們偽造的對象將會擁有“釋放檢測(release detectors)”來代替這些引用。釋放檢測器是一些很小的對象,它們會觀察發送給它們的釋放消息。當持有者想要放棄它的持有的時候,這些消息會發送給強引用對象。當我們釋放我們偽造的對象的時候,我們可以檢測哪些檢測器接收到了這些消息。只要知道哪些索引在偽造的對象的檢測器中,我們就可以找到原來Block中實際持有的對象。

blogimage_memoryleak_2.jpg

自動化

讓這工具真正閃光的是,在工程師內部構建的時候,它會連續的、自動的運行。

客戶端部分自動化是簡單的。我們在定時器上運行循環引用檢測器,定期掃描內存去尋找循環引用,雖然這不是完全沒有問題。當我們第一次運行分析器的時候,我們意識到它不足以很快的掃描整個內存空間。當它開始檢測的時候,我們需要給它提供一組候選對象。

為了更有效的解決這個問題,我們開發了FBAllocationTracker。這個工具會主動跟蹤NSObject子類的創建和釋放。它可以以一個很小的性能開銷來獲取任何類的任何實例。

對於客戶端的自動化,只要在NSTimer上使用FBRetainCycleDetector,再用FBAllocationTracker來抓取實例來配合跟蹤就行。

現在,讓我們來仔細看看後台會發生什麼。

循環引用可以包含任何數量的對象。一個壞的連接會導致很多環的時候,這就復雜了。

blogimage_memoryleak_3.jpg

在環中,A→B是一個壞連接,創建了兩個環:A-B-C-D 和 A-B-C-E。

這有兩個問題:

  • 我們不想給一個壞連接導致的兩個循環引用分別標記。

  • 我們不想給可能代表兩個問題的兩個循環引用一起標記,即使它們共享一個連接。

所以我們需要給循環引用定義簇組(clusters),鑒於這些啟發,我們寫了個算法來找到這些問題。

  • 在給定的時間收集所有的環。

  • 對於每一個環,提取Facebook特定的類名。

  • 對於每一個環,找到包含在環內的被報告的最小的環。

  • 依據上面的最小環,將環添加到組中。

  • 只報告最小環。

最後一部分是找出誰第一時間偶然引入了循環引用。我們可以通過環中的”git/hg責任”的部分代碼來猜測最近的變化所導致的問題。最後一個接觸這個代碼的人將會收到修復代碼的任務。

整個系統如下:

blogimage_memoryleak_4.jpg

手動性能分析

雖然自動化有助於簡化發現循環引用的過程,降低人員的消耗,手動性能分析依然有它的用武之地。我們創建的另一個工具允許任何人查看內存使用,甚至不需要把他的手機插到電腦上。

FBMemoryProfiler可以很容易的添加到任何應用程序,可以讓你手動配置構建文件,可以讓你在應用程序內運行循環應用檢測。它會借用FBAllocationTracker和FBRetainCycleDetector來實現此功能。

生成(Generations)

FBMemoryProfiler的一個很偉大的特性是“生成追蹤(generation tracking)”,類似於蘋果的Instruments的生成追蹤。生成只是簡單的在兩次標記之間拍攝所有仍然活著的對象的快照。

使用FBMemoryProfiler的界面,我們可以標記生成,例如,分配三個對象。然後我們標記另一個生成,之後繼續分配對象。第一個生成包含我們一開始的三個對象。如果任意一個對象被釋放了,它會從我們第二個生成中移除。

blogimage_memoryleak_5.jpg

當我們有一個重復的任務,我們認為可能會內存洩露的時候,生成追蹤是很有用的,例如,導航View Controller的進出。在每次開始我們的任務的時候,我們標記一個生成,然後,對之後的每個生成進行調查。如果一個對象不應該活這麼長時間,我們可以在FBMemoryProfiler界面清楚地看到。

Check Out

無論你的應用程序是大是小,功能是多是少,好的工程師都應有好的內存管理。在這些工具的幫助之下,我們可以更簡單的找到並修復這些內存洩露,所以我們可以花費更少的時間去手動處理,這樣就可以有更多的時間去編寫更好的代碼。我們也希望你可以發現它們是有用的。在Github上check out下來吧。FBRetainCycleDetector, FBAllocationTracker 和 FBMemoryProfiler。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved