程序性能優化以內存優化(三)上篇

阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
本篇文章將繼續從如下兩個內容來介紹內存優化:php

  • [內存抖動]
  • [內存泄漏]

其實大多數App或多或少都存在必定的內存泄漏狀況,這些內存泄漏可能存在於特定的運行環境時纔會發生。而內存泄漏堆積會引起嚴重後果OOM。內存抖動是指內存頻繁地分配和回收,而頻繁的gc會致使卡頓,嚴重時和內存泄漏同樣會致使OOM。html

接下來咱們一塊兒討論該如何查看以及解決這部分問題思路。java

1、內存泄漏

內存泄露是指程序中間動態分配了內存,但在程序結束時沒有釋放這部份內存,從而形成那部份內存不可用的狀況,重啓計算機能夠解決,但也有可能再次發生內存泄露,內存泄露和硬件沒有關係,它是由軟件設計缺陷引發的。android

簡單點說:應該被釋放的資源沒有被釋放。git

一、內存泄漏的種類

1)常發性內存泄漏。發生內存泄漏的代碼會被屢次執行到,每次被執行的時候都會致使一塊內存泄漏。github

2)偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操做過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。因此測試環境和測試方法對檢測內存泄漏相當重要。web

3) 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者因爲算法上的缺陷,致使總會有一塊僅且一塊內存發生泄漏。好比,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,因此內存泄漏只會發生一次。算法

4) 隱式內存泄漏。程序在運行過程當中不停的分配內存,可是直到結束的時候才釋放內存。嚴格的說這裏並無發生內存泄漏,由於最終程序釋放了全部申請的內存。可是對於一個服務器程序,須要運行幾天,幾周甚至幾個月,不及時釋放內存也可能致使最終耗盡系統的全部內存。因此,咱們稱這類內存泄漏爲隱式內存泄漏。api

二、爲何要修復內存泄漏

少許的內存泄漏可能不會引起什麼問題;可是內存泄漏累積,再多的內存也會被耗盡,最終致使OOM。緩存

2、定位內存泄漏

一、初步定位是否發生內存泄漏

藉助Android Studio的Monitor查看是否發生了內存泄漏狀況

經過反覆的執行同一個功能,觸發GC操做,觀察內存先後變化狀況。

image

若是內存先後未發生明顯變化(增長)此時能夠初步判斷未發生內存泄漏。

image

好比此時內存使用狀況爲:21.04MB,而後咱們打開一個新的Activity,而後返回執行GC操做,觀察此時的內存使用狀況。

二、Monitor欄基本功能說明:

image

序號一、手動觸發GC操做;

序號二、Dump Java Heap,獲取當前的堆棧信息,生成一個.hprof文件,AndroidStudio會自動使用HeapViewer打開;通常用於操做以後檢測內存泄漏的狀況;

序號三、Start Allocation Tracking 內存分配追蹤工具,用於追蹤一段時間的內存分配使用狀況,可以知道執行一些列操做後,有哪些對象被分配空間。通常用於追蹤某項操做以後的內存分配,調整相關的方法調用來優化app性能與內存使用;

序號四、剩餘可用 內存;

序號五、已經使用的內存;

三、Dump Java Heap進一步定位內存泄漏

經過Monitor欄只能初步粗略的觀察是否發生內存泄漏,然而要真正的發現內存泄漏以及精肯定位內存泄漏位置還須要藉助相關工具分析排查。

點擊Memory Monitor的Dump Java Heap,會生成一個.hprof文件,AndroidStudio會自動使用HeapViewer打開。

image

面板說明:

面板1:

Detect Leaked Activities :檢測泄漏的Activity

Find Duplicate Strings :查找重複的字符串

默認兩個選項都是勾選的。

image

點擊綠色箭頭,此時你們會看到Leaked Activities下有一個LaunchActvity@31...的信息,沒錯發生了內存泄漏,稍後咱們分析如何發生的內存泄漏。

面板2:

Total Count:該類的實例個數

Heap Count:選定的Heap中實例的個數

Sizeof:每一個實例佔用的內存大小

Shallow Size:全部該類的實例佔用的內存大小

Retained Size:該類的全部實例可支配的內存大小

面板3:

Instance:該類的全部實例對象(左側Total Count爲15,此處就有15個對象)

Depth:深度, GC Root點到該實例的最短鏈路數

Dominating Size:該實例可支配的內存大小

image

此時發現面板下有個實例存在。

面板4:

Reference Tree:引用樹

image

經過面板1咱們發現有一個Activity發生了泄漏。咱們能夠經過Reference Tree面板就能夠跟蹤到該實例的引用樹關係。

首先第一行咱們發現一個LaunchActivity實例存在,而後展開該實例進一步查看該實例的引用關係,第二行咱們能夠看出它是被LaunchActity匿名內部類持有(this$0),這個匿名內部類實例是callBack,緊接着會發現該實例在mPermissionUtil實例中持有。

此時,咱們能夠進入代碼查看該callBack是什麼,而後在mPermissionUtils的持有。

LaunchActivity中:

image

PermissionUitl中:

image

跟蹤代碼發現callback是一個接口,而後在LaunchActvity中調用了PermissionUtil的requestPermission(callback),而後將該callback賦值給PermissionUtil中成員引用,因爲PermissionUtil是一個單例,而後new PermissionCallBack()匿名內部類會默認持有外部類引用,此時它將持有外部類LaunchActivity的實例,而後有賦值給了PermissionUtil中的成員引用,因此形成的內存泄漏。這種內存泄漏稱之爲一次性內存泄漏,只會發生一次且只會泄漏最後一次調用者。

經過使用Androd Studio自帶的Dump Java Heap排查內存泄漏問題對於相對簡單的泄漏場景比較適合,若是發生較爲複雜的泄漏場景可能使用Dump Java Heap不太容易查找問題。此時咱們能夠藉助另一個工具:MAT (Memory Analyzer Tool)

四、MAT

Memory Analyzer Tool是Eclipse的一個插件,它的使用以及安裝這部分資料很是多,故篇幅緣由不在展開分析介紹。

下載地址:https://www.eclipse.org/mat/downloads.php

薦:https://blog.csdn.net/u010335298/article/details/52233689

薦:https://blog.csdn.net/itachi85/article/details/77075455

image

image

五、其餘

咱們也能夠藉助第三方檢測庫,在運行期間檢查內存泄漏狀況:LeakCanary

LeakCanary是square出品的一個檢測內存泄漏的庫,集成到App以後便無需關心,在發生內存泄漏以後會Toast、通知欄彈出等方式提示,能夠指出泄漏的引用路徑,並且能夠抓取當前的堆棧信息供詳細分析。

image

分析內存泄漏主要是定位GC Root,只有明白GC Root點纔可以準確分析定位內存泄漏問題。

3、內存抖動

內存抖動是指內存在短期內頻繁地分配和回收,而頻繁的gc會致使卡頓,嚴重時和內存泄漏同樣會致使OOM。

內存抖動爲何會形成OOM這關係到Java的垃圾回收。

一、常見內存抖動場景

循環中建立大量臨時對象;

onDraw中建立Paint或Bitmap對象等;

二、內存抖動後果

瞬間產生大量的對象會嚴重佔用Young Generation的內存區域,當達到閥值,剩餘空間不夠的時候,也會觸發GC。系統花費在GC上的時間越多,進行界面繪製或流音頻處理的時間就越短即便每次分配的對象佔用了不多的內存,可是他們疊加在一塊兒會增長Heap的壓力,從而觸發更多其餘類型的GC。這個操做有可能會影響到幀率,並使得用戶感知到性能問題。

image

4、onTrimMemory與onLowMemory

Android系統的每一個進程都有一個最大內存限制,若是申請的內存資源超過這個限制,系統就會拋出OOM錯誤。

onTrimMemory

因此在實際開發過程當中咱們要儘量避免內存泄漏與內存抖動以外,還要格外注意內存使用狀況。根據《Manage Your App's Memory》,咱們能夠對內存的狀態進行監聽,咱們的Application、Acivity、Service、ContentProvider與Fragment都實現了ComponentCallbacks2接口。因此可以重寫onTrimMemory與onLowMemory函數。

image

onTrimMemory的參數是一個int數值,表明不一樣的內存狀態:

TRIM_MEMORY_RUNNING_MODERATE:

你的應用正在運行而且不會被列爲可殺死的。可是設備此時正運行於低內存狀態下,系統開始觸發殺死LRU Cache中的Process的機制。

TRIM_MEMORY_RUNNING_LOW:

你的應用正在運行且沒有被列爲可殺死的。可是設備正運行於更低內存的狀態下,你應該釋放不用的資源用來提高系統性能。

TRIM_MEMORY_RUNNING_CRITICAL:

你的應用仍在運行,可是系統已經把LRU Cache中的大多數進程都已經殺死,所以你應該當即釋放全部非必須的資源。若是系統不能回收到足夠的RAM數量,系統將會清除全部的LRU緩存中的進程,而且開始殺死那些以前被認爲不該該殺死的進程,例如那個包含了一個運行態Service的進程。

當應用進程退到後臺正在被Cached的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:

TRIM_MEMORY_BACKGROUND:

系統正運行於低內存狀態而且你的進程正處於LRU緩存名單中最不容易殺掉的位置。儘管你的應用進程並非處於被殺掉的高危險狀態,系統可能已經開始殺掉LRU緩存中的其餘進程了。你應該釋放那些容易恢復的資源,以便於你的進程能夠保留下來,這樣當用戶回退到你的應用的時候纔可以迅速恢復。

TRIM_MEMORY_MODERATE:

系統正運行於低內存狀態而且你的進程已經已經接近LRU名單的中部位置。若是系統開始變得更加內存緊張,你的進程是有可能被殺死的。

TRIM_MEMORY_COMPLETE:

系統正運行於低內存的狀態而且你的進程正處於LRU名單中最容易被殺掉的位置。你應該釋聽任何不影響你的應用恢復狀態的資源。

TRIM_MEMORY_UI_HIDDEN:

UI不可見了,應該釋放佔用大量內存的UI數據。

好比說一個Bitmap,咱們緩存下來是爲了可能的(不必定)再次顯示。可是若是接到這個回調,那麼仍是將它釋放掉,若是回到前臺,再顯示會比較好。

onLowMemory

這個函數看名字就是低內存。這個函數的回調意味着後臺進程已經被幹掉了。這個回調能夠做爲4.0兼容onTrimMemory的TRIM_MEMORY_COMPLETE來使用

若是但願在其餘組件中也能接收到這些回調可使用上下文的registerComponentCallbacks註冊接收,

unRegisterComponentCallbacks反註冊

5、OutOfMemeory

OOM就是申請的內存超過了Heap的最大值。

image

OOM的產生不必定是一次申請的內存就超過了最大值,致使oom的緣由基本上都是通常狀況,咱們的不良代碼平時」積累」下來的。

咱們知道Android應用的進程都是從一個叫作Zygote的進程fork出來的。而且每一個應Aandroid會對其進行內存限制。咱們能夠查看:

image

6、有效減小內存佔用的建議

一、使用Android優化事後的集合

在Android開發時,咱們使用的大部分都是Java的api。其中咱們常常會用到java中的集合,好比HashMap。

使用HashMap很是舒服,可是對於Android這種內存敏感的移動平臺,不少時候使用這些Java的API並不能達到更好的性能,相反反而更消耗內存,因此針對Android,google也推出了更符合本身的API,好比SparseArray、ArrayMap用來代替HashMap在有些狀況下能帶來更好的性能提高。

注意:此處僅考慮內存佔用狀況,而且在必定的長度的數據集,並非適合全部場景下。

二、集合初始長度

如:HashMap,他的默認長度爲16,負載因子爲0.75,若是咱們知道要存放數據的長度如5,此時最合適的HashMap的初始容量爲:5/0.75 = 7;

故:HashMap map = new HashMap(7)

三、Bitmap

Bitmap能夠說是一個內存中的大胖子,做爲如今Android開發程序是比較幸福,有不少關於圖片加載優秀的庫,如Glide。

有關於Bitmap的優化咱們會在後續單獨專題中介紹,故不在此處展開介紹。

薦:《Handling Bitmaps》

四、try{}cacth(Error){}

對高風險OOM代碼塊如展現高清大圖等進行try catch,在catch塊加載非高清的圖片並作相應內存回收的處理。注意OOM是OutOfMemoryError,不能使用Exception進行捕獲。

五、解決全部內存泄漏問題

少許的內存泄漏可能不會帶來較爲明顯的影響,可是內存泄漏堆積的後果是很是嚴重的,再多的內存也會被耗盡,最終致使OOM發生。

六、避免內存抖動

儘可能避免在循環體或者頻繁調用的函數內建立對象,應該把對象建立移到循環體外。

另外還有一個經典的String拼接建立大量小的對象形成的內存抖動。

有時會發現頻繁的調用Log打印日誌,App會變卡頓。

Log.i(TAG,width+」x」+height);

這裏會產生2個新對象,width+」x」與width+」x」+height。

而TAG與x是編譯時就存在的字符串常量池,因此不算新對像。

因此通常來講咱們會對日誌輸出Log進行控制,或者使用StringBuilder進行優化。

七、onTrimMemory根據不一樣的內存狀態作相應處理

對於未實現ComponentCallbacks2組件,咱們須要爲其註冊ComponentCallbacks2。

image

7、總結

性能優化是一個長期實踐過程;大多數問題都是由通常問題形成的,而後這部分通常問題的積累最終會引起嚴重後果;壓死駱駝的可能就是最後一根稻草。在項目實際開發過程當中:要特別注意內存泄漏與內存抖動的場景,注意配合使用onTrimMemory完成內存的管理工做。

阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
原文連接:https://www.jianshu.com/p/607...

相關文章
相關標籤/搜索