Android性能優(yōu)化--內(nèi)存優(yōu)化

轉(zhuǎn)載自:Android性能優(yōu)化--內(nèi)存優(yōu)化

上一篇文章關(guān)于Android性能優(yōu)化--啟動(dòng)優(yōu)化探討了啟動(dòng)優(yōu)化相關(guān)的知識(shí)點(diǎn),在本篇將介紹內(nèi)存優(yōu)化的相關(guān)優(yōu)化。主要大綱參照如下

image

2.常見(jiàn)問(wèn)題

常見(jiàn)的Android內(nèi)存相關(guān)問(wèn)題,通常可以分為以下三種,內(nèi)存抖動(dòng)、內(nèi)存泄露、內(nèi)存溢出。

  • 內(nèi)存抖動(dòng):在短時(shí)間內(nèi)有大量的對(duì)象被創(chuàng)建或者被回收的現(xiàn)象,主要是循環(huán)中大量創(chuàng)建、回收對(duì)象。當(dāng)系統(tǒng)內(nèi)存不足,不斷GC內(nèi)存的時(shí)候,也有可能出現(xiàn)內(nèi)存抖動(dòng)情況。

  • 內(nèi)存泄露:當(dāng)一個(gè)對(duì)象不在使用了,本應(yīng)該被垃圾回收器回收。但是這個(gè)對(duì)象由于被其他正在使用的對(duì)象所持有,造成無(wú)法被回收的現(xiàn)象。

  • 內(nèi)存溢出:Android系統(tǒng)給每個(gè)應(yīng)用分配的內(nèi)存也有一個(gè)閥值,也就是Heap Size。當(dāng)應(yīng)用占用的內(nèi)存加上我們申請(qǐng)的內(nèi)存資源超過(guò)了系統(tǒng)分配的最大內(nèi)存時(shí)就會(huì)拋出的Out Of Memory異常。

上述三者之間的是一個(gè)遞進(jìn)關(guān)系,內(nèi)存抖動(dòng)<內(nèi)存泄露<內(nèi)存溢出。對(duì)于一般應(yīng)用主要是處理內(nèi)存抖動(dòng)和內(nèi)存泄露兩點(diǎn),處理好這兩點(diǎn)就會(huì)大大降低內(nèi)存溢出的可能性。

3.內(nèi)存管理

3.1 Java內(nèi)存管理

JVM的內(nèi)存回收對(duì)于大多數(shù)開(kāi)發(fā)者來(lái)說(shuō)接觸的并不是很多。因?yàn)镴VM本身是有一套內(nèi)存回收的機(jī)制,對(duì)于開(kāi)發(fā)者更多的是申請(qǐng)對(duì)象直接調(diào)用即可,其內(nèi)部并不是很在意。下面主要通過(guò)內(nèi)存存儲(chǔ)和回收這兩塊介紹。

  • 存儲(chǔ):JVM將可以存儲(chǔ)內(nèi)存的空間大概分為棧、本地方法棧、程序計(jì)數(shù)器、堆、方法區(qū)等模塊。

  • 棧:主要是針對(duì)方法使用的空間,當(dāng)JVM在執(zhí)行方法時(shí),會(huì)在此區(qū)域中創(chuàng)建一個(gè)棧幀來(lái)存放方法的各種信息,比如返回值,局部變量表和各種對(duì)象引用等。

  • 本地方法棧:專(zhuān)門(mén)提供給Native方法用的。

  • 程序計(jì)數(shù)器:記錄當(dāng)前執(zhí)行的位置。

  • 堆:幾乎所有對(duì)象、數(shù)組等都是在此分配內(nèi)存的,在JVM內(nèi)存中占的比例也是很大的,也是GC回收的主要陣地,平時(shí)我們說(shuō)的新生代、老年代、永久代也是指這片區(qū)域。

  • 方法區(qū):存放類(lèi)似類(lèi)定義、常量、編譯后的代碼、靜態(tài)變量等。

  • 回收:針對(duì)上述各個(gè)模塊的內(nèi)存回收,通常所說(shuō)的GC主要是對(duì)堆空間的回收,一般比較常用的方法為:標(biāo)記-清除算法、復(fù)制算法、分代收集算法等其它方法和其變形。

3.2 Android內(nèi)存管理

Android 系統(tǒng)主要是在Art和Dalvik虛擬機(jī)中的托管環(huán)境中跟蹤每個(gè)內(nèi)存分配,當(dāng)發(fā)現(xiàn)有可回收的對(duì)象,進(jìn)行內(nèi)存回收?;厥沼袃蓚€(gè)目標(biāo):在程序中查找將來(lái)無(wú)法訪(fǎng)問(wèn)的數(shù)據(jù)對(duì)象,并回收那些對(duì)象使用的資源 。

  • 進(jìn)程間的內(nèi)存管理:Android對(duì)于進(jìn)程間的內(nèi)存管理主要是通過(guò)內(nèi)核交換守護(hù)程序和onTrimMemory()進(jìn)程殺死來(lái)管理。
  • 內(nèi)核交換守護(hù)程序(kswapd):RAM中存在一個(gè)區(qū)域空間zRAM。當(dāng)設(shè)備上的可用內(nèi)存不足時(shí),守護(hù)程序?qū)⒆優(yōu)榛顒?dòng)狀態(tài)。kswapd可以將緩存的私有臟頁(yè)和匿名臟頁(yè)移動(dòng)到zRAM,并在其中進(jìn)行壓縮。
  • onTrimMemory:系統(tǒng)用于 onTrimMemory()通知應(yīng)用程序內(nèi)存即將用盡,并應(yīng)減少其分配。如果這還不夠,內(nèi)核將開(kāi)始?xì)⑺肋M(jìn)程以釋放內(nèi)存。它使用低內(nèi)存殺手(LMK)來(lái)執(zhí)行此操作。PS:LMK 這就會(huì)涉及到應(yīng)用保活等相關(guān)。
  • 應(yīng)用內(nèi)存管理:Android應(yīng)用內(nèi)內(nèi)存管理,主要是從Java層和Native 層優(yōu)化。本文主要介紹如何從Java層進(jìn)行內(nèi)存管理優(yōu)化,具體細(xì)節(jié)可以下面會(huì)一一介紹。

4.常見(jiàn)場(chǎng)景及解決方案

4.1 內(nèi)存抖動(dòng)

由于短時(shí)間內(nèi)有大量對(duì)象進(jìn)出Young Generiation區(qū)導(dǎo)致的,它伴隨著頻繁的GC。

  • 盡量避免在循環(huán)體內(nèi)創(chuàng)建對(duì)象,應(yīng)該把對(duì)象創(chuàng)建移到循環(huán)體外。
  • 注意自定義View的onDraw()方法會(huì)被頻繁調(diào)用,所以在這里面不應(yīng)該頻繁的創(chuàng)建對(duì)象。

如下面一部分代碼就對(duì)應(yīng)著內(nèi)存抖動(dòng)

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);    
  }
    };

通過(guò)Profile查看其內(nèi)存圖

image

可以看到其內(nèi)存圖基本上是一個(gè)鋸齒狀,是因?yàn)檫@時(shí)候一直在創(chuàng)建對(duì)象和回收對(duì)象所致。

4.2 內(nèi)存泄露

業(yè)內(nèi)一般對(duì)內(nèi)存泄露的原因總結(jié)為長(zhǎng)生命周期對(duì)象引用短生命周期對(duì)象,導(dǎo)致短生命周期對(duì)象無(wú)法及時(shí)回收所致。

1、單例引起的內(nèi)存泄漏

public static  FacebookAnalysis getInstance(Context context){
        if (facebookAnalysis == null){
      synchronized (FacebookAnalysis.class){  
 facebookAnalysis = new FacebookAnalysis(getAppEventsLoggerInstance(context));     
       }     
   }    
    return facebookAnalysis;    }

上面是一個(gè)常見(jiàn)的單例模式,如果參數(shù)引用ActivityContext,而單例模式的生命周期長(zhǎng)于Activity。這里單例模式引用Activity的實(shí)例,當(dāng)Activity被銷(xiāo)毀,Activity無(wú)法被回收,造成內(nèi)存泄露。如果這里引用的ApplicationContext,將無(wú)任何影響。因?yàn)?code>Application的生命周期與單例模式同樣長(zhǎng)。

2、靜態(tài)集合添加對(duì)象,在使用完之后未及時(shí)釋放。

 for (int i = 0; i < 10; i++) {  
   Object obj = new Object();          
  list.add(obj);           
 obj = null;   
     }

此時(shí)list是一個(gè)靜態(tài)的集合,obj單個(gè)對(duì)象,當(dāng)list集合使用完畢,應(yīng)當(dāng)及時(shí)清除該集合,避免obj被靜態(tài)對(duì)象引用。

3、 匿名內(nèi)部類(lèi)&非靜態(tài)內(nèi)部類(lèi)

Android 中常見(jiàn)的是對(duì)ListView中各個(gè)元素設(shè)置點(diǎn)擊事件,如果此時(shí)采用匿名內(nèi)部類(lèi),會(huì)存在內(nèi)存泄露的風(fēng)險(xiǎn)。常規(guī)做法是將該點(diǎn)擊事件用接口和setTag()的方式往外傳遞。

同樣Handler在使用過(guò)程中也會(huì)出現(xiàn)內(nèi)存泄露的風(fēng)險(xiǎn),一般則是采用弱引用的方式處理,或者在ActivityonDestroy方法中移除該Handler的所有消息`handler.removeCallbacksAndMessages(null)``。

4、線(xiàn)程泄露

在主線(xiàn)程Activity中出現(xiàn)如下代碼:

    public void onCreate(Bundle icicle) {    
    super.onCreate(icicle);      
  mIntent = (icicle == null) ? getIntent() : null;       
 new Thread(new Runnable() {      
      @Override           
 public void run() {          
      doSomeThing();         
   }   
     }).start();}

當(dāng)此時(shí)該Activity已經(jīng)銷(xiāo)毀,但是子線(xiàn)程中doSomeThing方法未執(zhí)行完成,此時(shí)會(huì)造成內(nèi)存泄露。一般做法是當(dāng)Activity銷(xiāo)毀時(shí)取消該線(xiàn)程或者采用其他方式實(shí)現(xiàn),總之原則是線(xiàn)程不持有Activity的上下文,如果持有,就應(yīng)及時(shí)取消。

5、數(shù)據(jù)庫(kù)游標(biāo),文件資源未及時(shí)關(guān)閉,廣播未反向注冊(cè),服務(wù)未解綁等行為。

6、Bitmap加載泄露

Bitmap的加載在Android一直是比較吃?xún)?nèi)存的,且容易出現(xiàn)內(nèi)存泄露相關(guān)問(wèn)題,一般都是采用統(tǒng)一的圖片請(qǐng)求框架去處理圖片加載緩存,這些框架都會(huì)從加載、壓縮、緩存等策略對(duì)其做優(yōu)化處理。同時(shí)Google 官方也是推薦使用統(tǒng)一庫(kù)處理位圖,具體可以在Glide官網(wǎng)查看。關(guān)于圖片加載這塊其實(shí)是很容易出現(xiàn)內(nèi)存泄露的問(wèn)題,在此暫時(shí)不作展開(kāi),后續(xù)會(huì)細(xì)說(shuō)。

5.常用工具

5.1 Memory Profiler

Memory ProfilerAndroid Profiler 中的一個(gè)組件,可幫助您識(shí)別可能會(huì)導(dǎo)致應(yīng)用卡頓、凍結(jié)甚至崩潰的內(nèi)存泄漏和內(nèi)存抖動(dòng)。它顯示一個(gè)應(yīng)用內(nèi)存使用量的實(shí)時(shí)圖表,可以捕獲堆轉(zhuǎn)儲(chǔ)、強(qiáng)制執(zhí)行垃圾回收以及跟蹤內(nèi)存分配。從圖中現(xiàn)象

image

可以看出應(yīng)用此時(shí)存在內(nèi)存抖動(dòng)的現(xiàn)象,此時(shí)抓取紅色部分,可以得到如下圖:

image
  • -A區(qū)域?yàn)橥献岩蓜?dòng)動(dòng)的部分
  • -B區(qū)域?yàn)榕判虬l(fā)現(xiàn)存在一組對(duì)象耗內(nèi)存
  • -D選中C區(qū)域中任一對(duì)象,即可看見(jiàn)具體類(lèi)為MainActivity,且可以看到行數(shù),右擊Jump to Source即可以跳入具體代碼。
5.2 Memory Analyzer

Memory Analyzer MAT是一個(gè)功能豐富的 JAVA 堆轉(zhuǎn)儲(chǔ)文件分析工具,可以幫助開(kāi)發(fā)者發(fā)現(xiàn)內(nèi)存漏洞和減少內(nèi)存消耗。根據(jù)上面分析下面這段代碼是存在內(nèi)存泄露的問(wèn)題:

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();
//        CallBackManager.removeCallBack(this);    
}    
@Override    public void doWork() {   
     // do work   
 }
}

連續(xù)進(jìn)入退出MemoryLeakActivity,通過(guò)Android Studio 的Profile可以查看到當(dāng)時(shí)的內(nèi)存入下圖

image

可以看到此時(shí)內(nèi)存處于一個(gè)波動(dòng)狀態(tài),保存該文件,生成memory-20191212T110510.hprof文件,通過(guò)命令轉(zhuǎn)換

D:\>hprof-conv D:\Android\log\memory-20191212T110510.hprof D:\Android\log\2.hprof

此時(shí)通過(guò)MAT打開(kāi)轉(zhuǎn)換后的文件如下:

image

選擇Histogram,輸入正則表達(dá)".MemoryLeak. "可以搜索到具體包名,然后右鍵List objects->With incoming references然后選擇Path to GC Roots->With all references(此處可以選擇其他)。此時(shí)可以看到下面這張圖

image

從圖中即可看出內(nèi)存泄露的位置,即該Activity被對(duì)象sCallbacks引用。在代碼中添加方法

    protected void onDestroy() {      
  super.onDestroy();       
 CallBackManager.removeCallBack(this);   
 }

再次抓取內(nèi)存信息,并未出現(xiàn)上述結(jié)果。

5.3 LeakCanary

可以通過(guò)LeakCanary在開(kāi)發(fā)階段檢測(cè)到引用的內(nèi)存情況。LeakCanary 主要是通過(guò)監(jiān)聽(tīng)ActivityonDestory,手動(dòng)調(diào)用GC,然后通過(guò)ReferenceQueue+WeakReference,來(lái)判斷Activity對(duì)象是否被回收,然后結(jié)合dump Heaphpof文件,通過(guò)Haha開(kāi)源庫(kù)分析泄露的位置。具體使用可以參照leakcanary。

6. 總結(jié)

關(guān)于內(nèi)存優(yōu)化知識(shí)點(diǎn)很多,很細(xì)。但究其根本我認(rèn)為是監(jiān)控內(nèi)存泄露和優(yōu)化內(nèi)存泄漏,各大廠(chǎng)商都有提過(guò)相關(guān)的方案

  • 美團(tuán)—Android線(xiàn)上OOM問(wèn)題定位組件
  • 微信 Android 終端內(nèi)存優(yōu)化實(shí)踐

這些都具備參考價(jià)值。同時(shí)我們也可以采用一些Hook黑科技相關(guān)方法進(jìn)行部分內(nèi)存性能消耗較大的業(yè)務(wù)進(jìn)行監(jiān)控,及時(shí)告知開(kāi)發(fā)人員。例如:可以通過(guò)Epic監(jiān)控項(xiàng)目中所有的setImageBitmap()方法,此時(shí)就可以知道傳入的Bitmap是否有內(nèi)存相關(guān)風(fēng)險(xiǎn),一旦有風(fēng)險(xiǎn),立馬通知反饋。

以上為此次Android內(nèi)存優(yōu)化的總結(jié),歡迎指正。

感謝:

---END---

推薦閱讀:優(yōu)雅?;罘桨?,原來(lái)Android還可以這樣?;?!
終于等到你!官方版Android源碼查看工具正式發(fā)布!
Flutter Interact 的 Flutter 1.12 大進(jìn)化和回顧Android高仿QQ 發(fā)送圖片時(shí)炫酷的加載效果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容