本文來自Dotry投稿,連結:https://www.jianshu.com/p/38b627adaecd
上一篇文章關於Android性能優化--啟動優化探討了啟動優化相關的知識點,在本篇將介紹內存優化的相關優化。主要大綱參照如下
2.常見問題常見的Android內存相關問題,通常可以分為以下三種,內存抖動、內存洩露、內存溢出。
內存抖動:在短時間內有大量的對象被創建或者被回收的現象,主要是循環中大量創建、回收對象。當系統內存不足,不斷GC內存的時候,也有可能出現內存抖動情況。
內存洩露:當一個對象不在使用了,本應該被垃圾回收器回收。但是這個對象由於被其他正在使用的對象所持有,造成無法被回收的現象。
內存溢出:Android系統給每個應用分配的內存也有一個閥值,也就是Heap Size。當應用佔用的內存加上我們申請的內存資源超過了系統分配的最大內存時就會拋出的Out Of Memory異常。
上述三者之間的是一個遞進關係,內存抖動<內存洩露<內存溢出。對於一般應用主要是處理內存抖動和內存洩露兩點,處理好這兩點就會大大降低內存溢出的可能性。
3.內存管理3.1 Java內存管理JVM的內存回收對於大多數開發者來說接觸的並不是很多。因為JVM本身是有一套內存回收的機制,對於開發者更多的是申請對象直接調用即可,其內部並不是很在意。下面主要通過內存存儲和回收這兩塊介紹。
存儲:JVM將可以存儲內存的空間大概分為棧、本地方法棧、程序計數器、堆、方法區等模塊。
棧:主要是針對方法使用的空間,當JVM在執行方法時,會在此區域中創建一個棧幀來存放方法的各種信息,比如返回值,局部變量表和各種對象引用等。
堆:幾乎所有對象、數組等都是在此分配內存的,在JVM內存中佔的比例也是很大的,也是GC回收的主要陣地,平時我們說的新生代、老年代、永久代也是指這片區域。
方法區:存放類似類定義、常量、編譯後的代碼、靜態變量等。
回收:針對上述各個模塊的內存回收,通常所說的GC主要是對堆空間的回收,一般比較常用的方法為:標記-清除算法、複製算法、分代收集算法等其它方法和其變形。
3.2 Android內存管理Android 系統主要是在Art和Dalvik虛擬機中的託管環境中跟蹤每個內存分配,當發現有可回收的對象,進行內存回收。回收有兩個目標:在程序中查找將來無法訪問的數據對象,並回收那些對象使用的資源 。
進程間的內存管理:Android對於進程間的內存管理主要是通過內核交換守護程序和onTrimMemory()進程殺死來管理。內核交換守護程序(kswapd):RAM中存在一個區域空間zRAM。當設備上的可用內存不足時,守護程序將變為活動狀態。kswapd可以將緩存的私有髒頁和匿名髒頁移動到zRAM,並在其中進行壓縮。onTrimMemory:系統用於 onTrimMemory()通知應用程式內存即將用盡,並應減少其分配。如果這還不夠,內核將開始殺死進程以釋放內存。它使用低內存殺手(LMK)來執行此操作。PS:LMK 這就會涉及到應用保活等相關。應用內存管理:Android應用內內存管理,主要是從Java層和Native 層優化。本文主要介紹如何從Java層進行內存管理優化,具體細節可以下面會一一介紹。4.常見場景及解決方案4.1 內存抖動由於短時間內有大量對象進出Young Generiation區導致的,它伴隨著頻繁的GC。
儘量避免在循環體內創建對象,應該把對象創建移到循環體外。注意自定義View的onDraw()方法會被頻繁調用,所以在這裡面不應該頻繁的創建對象。如下面一部分代碼就對應著內存抖動
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
for (int i =0;i<100;i++){
String string[] = new String[10000];
}
handler.sendEmptyMessageDelayed(1,30);
}
};通過Profile查看其內存圖
可以看到其內存圖基本上是一個鋸齒狀,是因為這時候一直在創建對象和回收對象所致。
4.2 內存洩露業內一般對內存洩露的原因總結為長生命周期對象引用短生命周期對象,導致短生命周期對象無法及時回收所致。
1、單例引起的內存洩漏
public static FacebookAnalysis getInstance(Context context){
if (facebookAnalysis == null){
synchronized (FacebookAnalysis.class){
facebookAnalysis = new FacebookAnalysis(getAppEventsLoggerInstance(context));
}
}
return facebookAnalysis;
}上面是一個常見的單例模式,如果參數引用Activity的Context,而單例模式的生命周期長於Activity。這裡單例模式引用Activity的實例,當Activity被銷毀,Activity無法被回收,造成內存洩露。如果這裡引用的Application的Context,將無任何影響。因為Application的生命周期與單例模式同樣長。
2、靜態集合添加對象,在使用完之後未及時釋放。
for (int i = 0; i < 10; i++) {
Object obj = new Object();
list.add(obj);
obj = null;
}此時list是一個靜態的集合,obj單個對象,當list集合使用完畢,應當及時清除該集合,避免obj被靜態對象引用。
3、 匿名內部類&非靜態內部類
Android 中常見的是對ListView中各個元素設置點擊事件,如果此時採用匿名內部類,會存在內存洩露的風險。常規做法是將該點擊事件用接口和setTag()的方式往外傳遞。
同樣Handler在使用過程中也會出現內存洩露的風險,一般則是採用弱引用的方式處理,或者在Activity 的onDestroy方法中移除該Handler的所有消息`handler.removeCallbacksAndMessages(null)``。
4、線程洩露
在主線程Activity中出現如下代碼:
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mIntent = (icicle == null) ? getIntent() : null;
new Thread(new Runnable() {
@Override
public void run() {
doSomeThing();
}
}).start();
}當此時該Activity已經銷毀,但是子線程中doSomeThing方法未執行完成,此時會造成內存洩露。一般做法是當Activity銷毀時取消該線程或者採用其他方式實現,總之原則是線程不持有Activity的上下文,如果持有,就應及時取消。
5、資料庫遊標,文件資源未及時關閉,廣播未反向註冊,服務未解綁等行為。
6、Bitmap加載洩露
Bitmap的加載在Android一直是比較吃內存的,且容易出現內存洩露相關問題,一般都是採用統一的圖片請求框架去處理圖片加載緩存,這些框架都會從加載、壓縮、緩存等策略對其做優化處理。同時Google 官方也是推薦使用統一庫處理位圖,具體可以在Glide官網查看。關於圖片加載這塊其實是很容易出現內存洩露的問題,在此暫時不作展開,後續會細說。
5.常用工具5.1 Memory ProfilerMemory Profiler 是 Android Profiler 中的一個組件,可幫助您識別可能會導致應用卡頓、凍結甚至崩潰的內存洩漏和內存抖動。它顯示一個應用內存使用量的實時圖表,可以捕獲堆轉儲、強制執行垃圾回收以及跟蹤內存分配。從圖中現象
可以看出應用此時存在內存抖動的現象,此時抓取紅色部分,可以得到如下圖:
-D選中C區域中任一對象,即可看見具體類為MainActivity,且可以看到行數,右擊Jump to Source即可以跳入具體代碼。5.2 Memory Analyzer
Memory Analyzer MAT是一個功能豐富的 JAVA 堆轉儲文件分析工具,可以幫助開發者發現內存漏洞和減少內存消耗。根據上面分析下面這段代碼是存在內存洩露的問題:
public class CallBackManager {
public static ArrayList<CallBack> sCallBacks = new ArrayList<>();
public static void addCallBack(CallBack callBack) {
sCallBacks.add(callBack);
}
public static void removeCallBack(CallBack callBack) {
sCallBacks.remove(callBack);
}
}public class MemoryLeakActivity extends AppCompatActivity implements CallBack{
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memoryleak);
ImageView imageView = findViewById(R.id.iv_memoryleak);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.splash);
imageView.setImageBitmap(bitmap);
CallBackManager.addCallBack(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void doWork() {
}
}連續進入退出MemoryLeakActivity,通過Android Studio 的Profile可以查看到當時的內存入下圖
可以看到此時內存處於一個波動狀態,保存該文件,生成memory-20191212T110510.hprof文件,通過命令轉換
D:\>hprof-conv D:\Android\log\memory-20191212T110510.hprof D:\Android\log\2.hprof此時通過MAT打開轉換後的文件如下:
選擇Histogram,輸入正則表達".MemoryLeak. "可以搜索到具體包名,然後右鍵List objects->With incoming references然後選擇Path to GC Roots->With all references(此處可以選擇其他)。此時可以看到下面這張圖
從圖中即可看出內存洩露的位置,即該Activity被對象sCallbacks引用。在代碼中添加方法
protected void onDestroy() {
super.onDestroy();
CallBackManager.removeCallBack(this);
}再次抓取內存信息,並未出現上述結果。
5.3 LeakCanary可以通過LeakCanary在開發階段檢測到引用的內存情況。LeakCanary 主要是通過監聽Activity的onDestory,手動調用GC,然後通過ReferenceQueue+WeakReference,來判斷Activity對象是否被回收,然後結合dump Heap的hpof文件,通過Haha開源庫分析洩露的位置。具體使用可以參照leakcanary。
6. 總結關於內存優化知識點很多,很細。但究其根本我認為是監控內存洩露和優化內存洩漏,各大廠商都有提過相關的方案
這些都具備參考價值。同時我們也可以採用一些Hook黑科技相關方法進行部分內存性能消耗較大的業務進行監控,及時告知開發人員。例如:可以通過Epic監控項目中所有的setImageBitmap()方法,此時就可以知道傳入的Bitmap是否有內存相關風險,一旦有風險,立馬通知反饋。
以上為此次Android內存優化的總結,歡迎指正。
感謝:
https://developer.android.google.cn/topic/performance/memory-overviewhttps://time.geekbang.org/column/article/71610https://coding.imooc.com/learn/list/308.html