1. 程式人生 > >Android App解決卡頓慢之記憶體抖動及記憶體洩漏(發現和定位)

Android App解決卡頓慢之記憶體抖動及記憶體洩漏(發現和定位)

記憶體抖動是指在短時間內有大量的物件被建立或者被回收的現象,記憶體抖動出現原因主要是頻繁(很重要)在迴圈裡建立物件(導致大量物件在短時間內被建立,由於新物件是要佔用記憶體空間的而且是頻繁,如果一次或者兩次在迴圈裡建立物件對記憶體影響不大,不會造成嚴重記憶體抖動這樣可以接受也不可避免,頻繁的話就很記憶體抖動很嚴重),記憶體抖動的影響是如果抖動很頻繁,會導致垃圾回收機制頻繁執行(短時間內產生大量物件,需要大量記憶體,而且還是頻繁抖動,就可能會需要回收記憶體以用於產生物件,垃圾回收機制就自然會頻繁運行了)。綜上就是頻繁記憶體抖動會導致垃圾回收頻繁執行。

記憶體洩漏是指某一段記憶體在程式裡功能上已經不需要了,但是垃圾回收機制回收記憶體時檢測那段記憶體還是被需要的,不能被回收,這種在程式中在沒有使用的但是又不能被回收的記憶體就是被洩漏的記憶體,那為什麼會這樣呢?正常的話應該是程式裡不需要的記憶體就可以被回收,這是垃圾回收機制做的事呀,如果垃圾回收機制正常執行的情況下,不應該這樣啊,但是實際就是垃圾回收機制正常的情況下發生的記憶體洩漏。其實到這裡java程式設計師就得知道垃圾回收機制中,判斷一段記憶體是否是垃圾,是否可回收的條件,這個條件是通過檢查這段記憶體是否存在引用和被引用關係,不存在這關係時,就認為可回收,若還存在引用或被引用關係,就認為不可回收,現在就可以知道導致記憶體洩漏的原因是程式設計師沒有將不用的記憶體去掉引用關係(因為程式中大多記憶體石油物件指向的,所以去掉引用關係就是置空)。記憶體洩漏會導致一些記憶體沒法被正常利用,話句話就是可以使用記憶體變少了,這樣輕則增加垃圾回收機制執行頻率,重則記憶體溢位(當系統需要分配一段記憶體,但是現有記憶體在垃圾回收執行後任然不足時,就會記憶體溢位);為避免記憶體洩漏,在寫程式時已經確定不需要的引用型變數,就置空;雖然即使記憶體沒洩露,也有可能出現記憶體溢位,這時的記憶體溢位就是有別的問題導致的。

1) Memory Churn and performance(記憶體抖動和效能)

雖然Android有自動管理記憶體的機制,但是對記憶體的不恰當使用仍然容易引起嚴重的效能問題。在同一幀裡面建立過多的物件是件需要特別引起注意的事情。

Android系統裡面有一個Generational Heap Memory的模型,系統會根據記憶體中不同的記憶體資料型別分別執行不同的GC操作。例如,最近剛分配的物件會放在Young Generation區域,這個區域的物件通常都是會快速被建立並且很快被銷燬回收的,同時這個區域的GC操作速度也是比Old Generation區域的GC操作速度更快的。

 

除了速度差異之外,執行GC

操作的時候,任何執行緒的任何操作都會需要暫停,等待GC操作完成之後,其他操作才能夠繼續執行(所以垃圾回收執行的次數越少,對效能的影響就越少)

 

通常來說,單個的GC並不會佔用太多時間,但是大量不停的GC操作則會顯著佔用幀間隔時間(16ms)。如果在幀間隔時間裡面做了過多的GC操作,那麼自然其他類似計算,渲染等操作的可用時間就變得少了。

導致GC頻繁執行有兩個原因:

·Memory Churn記憶體抖動,記憶體抖動是因為大量的物件被建立又在短時間內馬上被釋放。

·瞬間產生大量的物件會嚴重佔用Young Generation的記憶體區域,當達到閥值,剩餘空間不夠的時候,也會觸發GC。即使每次分配的物件佔用了很少的記憶體,但是他們疊加在一起會增加

Heap的壓力,從而觸發更多其他型別的GC。這個操作有可能會影響到幀率,並使得使用者感知到效能問題(幀率是Android渲染機制中的概念,導致卡頓慢的直接原因,就是渲染機制受阻,關於渲染機制有另一篇部落格特別說了,想了解的可以點選這裡)。

 

解決上面的問題有簡潔直觀方法,如果你在Memory Monitor裡面檢視到短時間發生了多次記憶體的漲跌,這意味著很有可能發生了記憶體抖動。

 

同時我們還可以通過Allocation Tracker來檢視在短時間內,同一個棧中不斷進出的相同物件。這是記憶體抖動的典型訊號之一。

當你大致定位問題之後,接下去的問題修復也就顯得相對直接簡單了。例如,你需要避免在for迴圈裡面分配物件佔用記憶體,需要嘗試把物件的建立移到迴圈體之外,自定義View中的onDraw方法也需要引起注意,每次螢幕發生繪製以及動畫執行過程中,onDraw方法都會被呼叫到,避免在onDraw方法裡面執行復雜的操作,避免建立物件。對於那些無法避免需要建立物件的情況,我們可以考慮物件池模型,通過物件池來解決頻繁建立與銷燬的問題,但是這裡需要注意結束使用之後,需要手動釋放物件池中的物件。

2) Garbage Collection in Android(Android垃圾回收)

JVM的回收機制給開發人員帶來很大的好處,不用時刻處理物件的分配與回收,可以更加專注於更加高階的程式碼實現。相比起JavaCC++等語言具備更高的執行效率,他們需要開發人員自己關注物件的分配與回收,但是在一個龐大的系統當中,還是免不了經常發生部分物件忘記回收的情況,這就是記憶體洩漏。

原始JVM中的GC機制在Android中得到了很大程度上的優化。Android裡面是一個三級Generation的記憶體模型,最近分配的物件會存放在Young Generation區域,當這個物件在這個區域停留的時間達到一定程度,它會被移動到Old Generation,最後到Permanent Generation區域。

 

每一個級別的記憶體區域都有固定的大小,此後不斷有新的物件被分配到此區域,當這些物件總的大小快達到這一級別記憶體區域的閥值時,會觸發GC的操作,以便騰出空間來存放其他新的物件。

 

前面提到過每次GC發生的時候,所有的執行緒都是暫停狀態的。GC所佔用的時間和它是哪一個Generation也有關係,Young Generation的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長。執行時間的長短也和當前Generation中的物件數量有關,遍歷查詢20000個物件比起遍歷50個物件自然是要慢很多的。

雖然Google的工程師在儘量縮短每次GC所花費的時間,但是特別注意GC引起的效能問題還是很有必要。如果不小心在最小的for迴圈單元裡面執行了建立物件的操作,這將很容易引起GC並導致效能問題。通過Memory Monitor我們可以檢視到記憶體的佔用情況,每一次瞬間的記憶體降低都是因為此時發生了GC操作,如果在短時間內發生大量的記憶體上漲與降低的事件(記憶體嚴重抖動),這說明很有可能這裡有效能問題。我們還可以通過Heap and Allocation Tracker工具來檢視此時記憶體中分配的到底有哪些物件。

到這裡為止就簡單介紹了記憶體抖動(概念及判斷方法,方法是兩個工具使用,一個用於判斷有沒有嚴重記憶體抖動Memory Monitor,一個用於確認抖動位置Heap and Allocation Tracker),及較詳細的介紹了Android中垃圾回收機制。

2) Performance Cost of Memory Leaks(記憶體洩漏)

雖然Java有自動回收的機制,可是這不意味著Java中不存在記憶體洩漏的問題,而記憶體洩漏會很容易導致嚴重的效能問題。

記憶體洩漏指的是那些程式不再使用的物件無法被GC識別,這樣就導致這個物件一直留在記憶體當中,佔用了寶貴的記憶體空間。顯然,這還使得每級Generation的記憶體區域可用空間變小,GC就會更容易被觸發,從而引起效能問題。

尋找記憶體洩漏並修復這個漏洞是件很棘手的事情,你需要對執行的程式碼很熟悉,清楚的知道在特定環境下是如何執行的,然後仔細排查。例如,你想知道程式中的某個activity退出的時候,它之前所佔用的記憶體是否有完整的釋放乾淨了?首先你需要在activity處於前臺的時候使用Heap Tool獲取一份當前狀態的記憶體快照,然後你需要建立一個幾乎不這麼佔用記憶體的空白activity用來給前一個Activity進行跳轉,其次在跳轉到這個空白的activity的時候主動呼叫System.gc()方法來確保觸發一個GC操作。最後,如果前面這個activity的記憶體都有全部正確釋放,那麼在空白activity被啟動之後的記憶體快照中應該不會有前面那個activity中的任何物件了。


關於記憶體抖動和記憶體洩漏就到這裡了,接下來就說一下Android studio 提供的記憶體優化方面的工具

Android Studio提供了工具來幫助開發者發現和解決記憶體抖動和記憶體洩漏。

 Tool - Memory Monitor(用於發現記憶體抖動及記憶體洩漏的)

Android Studio中的Memory Monitor可以很好的幫組我們檢視程式的記憶體使用情況。



以下內容很重要,以下內容很重要,以下內容很重要重要的事情說三遍

·Memory Monitor:檢視整個app所佔用的記憶體,以及發生GC的時刻,短時間內發生大量的GC操作是一個危險的訊號(用於發現有沒有記憶體洩漏和嚴重記憶體抖動)。

後面兩個是用於定位的記憶體抖動和記憶體洩漏發生的具體位置·

Allocation Tracker:使用此工具來追蹤記憶體的分配,前面有提到過。

Heap Tool:檢視當前記憶體快照,便於對比分析哪些物件有可能是洩漏了的.

現在就可以定位到某一段程式碼發生了記憶體洩漏或抖動。

如果是記憶體洩漏解決方法就很直接,在適當的時候把洩漏的物件置空就可以了。

但是如果只記憶體抖動的話就得分兩種情況,由於記憶體抖動是在短時間內建立釋放大量物件導致的(一般是迴圈內建立物件),直接辦法就是不再短時間內建立大量物件,如果建立物件的過程可以拿到迴圈外而不影響功能,這種情況比較容易解決。但是更多的是另一種情況,就是不能拿到迴圈外,否則影響功能。對於第二種情況就要做到再迴圈內建立物件,但是又要控制物件個數。這種情況還是下一篇在說明解決辦法。

.