內(nèi)存優(yōu)化

內(nèi)存簡(jiǎn)介

RAM(random access memory)隨機(jī)存取存儲(chǔ)器。說白了就是內(nèi)存。
一般Java在內(nèi)存分配時(shí)會(huì)涉及到以下區(qū)域:

  • 寄存器(Registers):速度最快的存儲(chǔ)場(chǎng)所,因?yàn)榧拇嫫魑挥谔幚砥鲀?nèi)部唉堪,我們?cè)诔绦蛑袩o法控制
  • 棧(Stack):存放基本類型的數(shù)據(jù)和對(duì)象的引用院喜,但對(duì)象本身不存放在棧中,而是存放在堆中
  • 堆(Heap):堆內(nèi)存用來存放由new創(chuàng)建的對(duì)象和數(shù)組录豺。在堆中分配的內(nèi)存朦肘,由Java虛擬機(jī)的自動(dòng)垃圾回收器(GC)來管理。
  • 靜態(tài)域(static field): 靜態(tài)存儲(chǔ)區(qū)域就是指在固定的位置存放應(yīng)用程序運(yùn)行時(shí)一直存在的數(shù)據(jù)双饥,Java在內(nèi)存中專門劃分了一個(gè)靜態(tài)存儲(chǔ)區(qū)域來管理一些特殊的數(shù)據(jù)變量如靜態(tài)的數(shù)據(jù)變量
  • 常量池(constant pool):虛擬機(jī)必須為每個(gè)被裝載的類型維護(hù)一個(gè)常量池媒抠。常量池就是該類型所用到常量的一個(gè)有序集和,包括直接常量(string,integer和floating point常量)和對(duì)其他類型咏花,字段和方法的符號(hào)引用趴生。
  • 非RAM存儲(chǔ):硬盤等永久存儲(chǔ)空間

堆棧特點(diǎn)對(duì)比:

由于篇幅原因,下面只簡(jiǎn)單的介紹一下堆棧的一些特性昏翰。

  • :當(dāng)定義一個(gè)變量時(shí)冲秽,Java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)該變量退出該作用域后矩父,Java會(huì)自動(dòng)釋放掉為該變量所分配的內(nèi)存空間锉桑,該內(nèi)存空間可以立即被另作他用。
    :當(dāng)堆中的new產(chǎn)生數(shù)組和對(duì)象超出其作用域后窍株,它們不會(huì)被釋放民轴,只有在沒有引用變量指向它們的時(shí)候才變成垃圾,不能再被使用球订。即使這樣后裸,所占內(nèi)存也不會(huì)立即釋放,而是等待被垃圾回收器收走冒滩。這也是Java比較占內(nèi)存的原因微驶。

  • :存取速度比堆要快,僅次于寄存器开睡。但缺點(diǎn)是因苹,存在棧中的數(shù)據(jù)大小與生存期必須是確定的,缺乏靈活性篇恒。
    :堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)扶檐,可以動(dòng)態(tài)地分配內(nèi)存大小,因此存取速度較慢胁艰。也正因?yàn)檫@個(gè)特點(diǎn)款筑,堆的生存期不必事先告訴編譯器,而且Java的垃圾收集器會(huì)自動(dòng)收走這些不再使用的數(shù)據(jù)腾么。

  • :棧中的數(shù)據(jù)可以共享奈梳, 它是由編譯器完成的,有利于節(jié)省空間解虱。
    例如:需要定義兩個(gè)變量int a = 3攘须;int b = 3;
    編譯器先處理int a = 3饭寺;首先它會(huì)在棧中創(chuàng)建一個(gè)變量為a的引用阻课,然后查找棧中是否有3這個(gè)值,如果沒找到艰匙,就將3存放進(jìn)來限煞,然后將a指向3。接著處理int b = 3员凝;在創(chuàng)建完b的引用變量后署驻,因?yàn)樵跅V幸呀?jīng)有3這個(gè)值,便將b直接指向3健霹。這樣旺上,就出現(xiàn)了a與b同時(shí)均指向3的情況。這時(shí)糖埋,如果再讓a=4宣吱;那么編譯器會(huì)重新搜索棧中是否有4值,如果沒有瞳别,則將4存放進(jìn)來征候,并讓a指向4;如果已經(jīng)有了祟敛,則直接將a指向這個(gè)地址疤坝。因此a值的改變不會(huì)影響到b的值。
    :例如上面棧中a的修改并不會(huì)影響到b, 而在堆中一個(gè)對(duì)象引用變量修改了這個(gè)對(duì)象的內(nèi)部狀態(tài)馆铁,會(huì)影響到另一個(gè)對(duì)象引用變量跑揉。

Android中的垃圾回收機(jī)制


Android平臺(tái)最吸引開發(fā)者的一個(gè)特性:有垃圾回收機(jī)制,無需手動(dòng)管理內(nèi)存埠巨,Android系統(tǒng)會(huì)自動(dòng)跟蹤所有的

Paste_Image.png

Young Generation

  • 大多數(shù)新建的對(duì)象都位于Eden區(qū)
  • 當(dāng)Eden區(qū)被對(duì)象填滿時(shí)历谍,就會(huì)執(zhí)行Minor GC.并把所有存活下來的對(duì)象轉(zhuǎn)移到其中一個(gè)survivor區(qū)
  • Survivor Space: S0、S1有兩個(gè)辣垒,存放每次垃圾回收所存活的對(duì)象
  • Minor GC同樣會(huì)檢查survivor區(qū)中存活下來的對(duì)象扮饶,并把它們轉(zhuǎn)移到另一個(gè)survivor區(qū)。這樣在一段時(shí)間內(nèi)乍构,總會(huì)有一個(gè)空的survivor區(qū)甜无。

Old Generation

  • 存放長(zhǎng)期存活的對(duì)象和經(jīng)過多次Minor GC后依然存活下來的對(duì)象
  • 滿了進(jìn)行Major GC

Parmanent Generation:

  • 存放方法區(qū),方法區(qū)中有要加載的類信息哥遮、靜態(tài)變量岂丘、final類型的常量、屬性和方法信息

垃圾回收機(jī)制&FPS

  • Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào)眠饮,觸發(fā)對(duì)UI進(jìn)行渲染奥帘,那么整個(gè)過程如果保證在16ms以內(nèi)就能達(dá)到一個(gè)流暢的畫面即為60FPS。
  • 如果某一幀的操作超過了16ms就會(huì)讓用戶感覺到卡頓仪召。
  • UI渲染過程發(fā)生GC寨蹋,導(dǎo)致某一幀繪制時(shí)間超過16ms.

內(nèi)存泄漏

內(nèi)存泄漏是指某一段內(nèi)存在程序里功能上已經(jīng)不需要了松蒜,但是垃圾回收機(jī)制回收內(nèi)存時(shí)檢測(cè)那段內(nèi)存還是被需要的,不能被回收已旧,這種在程序中在沒有使用的但是又不能被回收的內(nèi)存就是被泄漏的內(nèi)存秸苗,那為什么會(huì)這樣呢?
正常的話應(yīng)該是程序里不需要的內(nèi)存就可以被回收运褪,這是垃圾回收機(jī)制做的事呀惊楼,如果垃圾回收機(jī)制正常運(yùn)行的情況下,不應(yīng)該這樣啊秸讹,但是實(shí)際就是垃圾回收機(jī)制正常的情況下發(fā)生的內(nèi)存泄漏檀咙。其實(shí)到這里java程序員就得知道垃圾回收機(jī)制中,判斷一段內(nèi)存是否是垃圾璃诀,是否可回收的條件弧可,這個(gè)條件是通過檢查這段內(nèi)存是否存在引用和被引用關(guān)系,不存在這關(guān)系時(shí)劣欢,就認(rèn)為可回收侣诺,若還存在引用或被引用關(guān)系,就認(rèn)為不可回收氧秘,現(xiàn)在就可以知道導(dǎo)致內(nèi)存泄漏的原因是程序員沒有將不用的內(nèi)存去掉引用關(guān)系(因?yàn)槌绦蛑写蠖鄡?nèi)存石油對(duì)象指向的年鸳,所以去掉引用關(guān)系就是置空)。內(nèi)存泄漏會(huì)導(dǎo)致一些內(nèi)存沒法被正常利用丸相,話句話就是可以使用內(nèi)存變少了搔确,這樣輕則增加垃圾回收機(jī)制運(yùn)行頻率,重則內(nèi)存溢出(當(dāng)系統(tǒng)需要分配一段內(nèi)存灭忠,但是現(xiàn)有內(nèi)存在垃圾回收運(yùn)行后任然不足時(shí)膳算,就會(huì)內(nèi)存溢出);為避免內(nèi)存泄漏弛作,在寫程序時(shí)已經(jīng)確定不需要的引用型變量涕蜂,就置空;雖然即使內(nèi)存沒泄露映琳,也有可能出現(xiàn)內(nèi)存溢出机隙,這時(shí)的內(nèi)存溢出就是有別的問題導(dǎo)致的。

  • 應(yīng)用程序分配了大量不能被回收的對(duì)象
  • 系統(tǒng)可分配內(nèi)存越來越少
  • 新對(duì)象的創(chuàng)建需要的內(nèi)存不夠
  • GC之后再分配
  • 60fps

舉例

  • 非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例造成的泄露
    public class MainActivity extends Acitivity{
    private static TestResource sresource=null;
    @Override
    protected void onCreate(Bundle saveInstanceState){
    super.onCreate(saveInstanceState);
    setContentView(R.layout.activity_main);
    if (sresource==null){
    sresource=new TestResource();
    }
    }
    class TestResource{
    //...
    }

上面的代碼中的sresource實(shí)例類型為靜態(tài)實(shí)例萨西,在第一個(gè)MainActivity act1實(shí)例創(chuàng)建時(shí)有鹿,sresource會(huì)獲得并一直持有act1的引用。當(dāng)MainAcitivity銷毀后重建谎脯,因?yàn)閟resource持有act1的引用葱跋,所以act1是無法被GC回收的,進(jìn)程中會(huì)存在2個(gè)MainActivity實(shí)例(act1和重建后的MainActivity實(shí)例),這個(gè)act1對(duì)象就是一個(gè)無用的但一直占用內(nèi)存的對(duì)象娱俺,即無法回收的垃圾對(duì)象稍味。所以,對(duì)于啟動(dòng)模式不是單一模式的Activity荠卷, 應(yīng)該避免在activity里面實(shí)例化其非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例模庐。

  • 調(diào)用Context

  • Handler的引用(非靜態(tài)內(nèi)部類是持有外部類類引用)
    public class SampleActivity extends Activity {

        private final Handler mHandler = new Handler() { 
          @Override 
          public void handleMessage(Message msg) { 
            // ... 
          } 
        } 
    
        @Override 
        protected void onCreate(Bundle savedInstanceState) { 
          super.onCreate(savedInstanceState); 
    
          // 發(fā)送一個(gè)10分鐘后執(zhí)行的一個(gè)消息 
          mHandler.postDelayed(new Runnable() { 
            @Override 
            public void run() { } 
          }, 600000); 
    
          // 結(jié)束當(dāng)前的Activity 
          finish(); 
        } 
      } 
    

解決方法
public class SampleActivity extends Activity {
private final WeakReference<SampleActivity> mActivity;
/**
* 使用靜態(tài)的內(nèi)部類,不會(huì)持有當(dāng)前對(duì)象的引用
*/
private static class MyHandler extends Handler {
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}

        @Override 
        public void handleMessage(Message msg) { 
          SampleActivity activity = mActivity.get(); 
          if (activity != null) { 
            // ... 
          } 
        } 
      } 
        private final MyHandler mHandler = new MyHandler(this); 
      /** 
       * 使用靜態(tài)的內(nèi)部類僵朗,不會(huì)持有當(dāng)前對(duì)象的引用 
       */ 
      private static final Runnable sRunnable = new Runnable() { 
          @Override 
          public void run() { } 
      }; 

      @Override 
      protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 

        //  發(fā)送一個(gè)10分鐘后執(zhí)行的一個(gè)消息 
        mHandler.postDelayed(sRunnable, 600000); 

        // 結(jié)束 
        finish(); 
      } 
    } 

線程引發(fā)的內(nèi)存泄漏---變成靜態(tài)
集合對(duì)象沒有清理
將集合設(shè)為null
資源對(duì)象沒有關(guān)閉(在onDestory中關(guān)閉)

避免內(nèi)存泄露的方法:

  • 盡量不要讓靜態(tài)變量引用Activity
  • 使用WeakReference
  • 使用靜態(tài)內(nèi)部類代替內(nèi)部類
  • 靜態(tài)內(nèi)部類使用弱引用來引用外部類
  • 在聲名周期結(jié)束的時(shí)候釋放資源

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

指在短時(shí)間內(nèi)有大量的對(duì)象被創(chuàng)建或者被回收的現(xiàn)象,內(nèi)存抖動(dòng)出現(xiàn)原因主要是頻繁(很重要)在循環(huán)里創(chuàng)建對(duì)象(導(dǎo)致大量對(duì)象在短時(shí)間內(nèi)被創(chuàng)建屑彻,由于新對(duì)象是要占用內(nèi)存空間的而且是頻繁验庙,如果一次或者兩次在循環(huán)里創(chuàng)建對(duì)象對(duì)內(nèi)存影響不大,不會(huì)造成嚴(yán)重內(nèi)存抖動(dòng)這樣可以接受也不可避免社牲,頻繁的話就很內(nèi)存抖動(dòng)很嚴(yán)重)粪薛,內(nèi)存抖動(dòng)的影響是如果抖動(dòng)很頻繁,會(huì)導(dǎo)致垃圾回收機(jī)制頻繁運(yùn)行(短時(shí)間內(nèi)產(chǎn)生大量對(duì)象搏恤,需要大量?jī)?nèi)存违寿,而且還是頻繁抖動(dòng),就可能會(huì)需要回收內(nèi)存以用于產(chǎn)生對(duì)象熟空,垃圾回收機(jī)制就自然會(huì)頻繁運(yùn)行了)藤巢。綜上就是頻繁內(nèi)存抖動(dòng)會(huì)導(dǎo)致垃圾回收頻繁運(yùn)行。

內(nèi)存檢測(cè)工具

  • Memory Monitor
    方便顯示內(nèi)存使用和GC情況
    快速定位卡頓是否和GC有關(guān)
    快速定位Crash是否和內(nèi)存占用過高有關(guān)
    快速定位潛在的內(nèi)存泄漏問題
    簡(jiǎn)單易用
    不能準(zhǔn)確定位問題

  • Allocation Tracker
    定位代碼中分配的對(duì)象的類型息罗,大小掂咒、時(shí)間、線程迈喉、堆棧等信息
    定位內(nèi)存抖動(dòng)問題
    配合Heap Viewer一起定位內(nèi)存泄漏問題

  • Heap Viewer
    內(nèi)存快照信息
    每次GC之后收集

減少內(nèi)存使用

  • 使用更輕量的數(shù)據(jù)結(jié)構(gòu)(比如SpareArray代替HashMap)
  • 避免在onDraw方法中創(chuàng)建對(duì)象
  • 對(duì)象池(Message.obtin())
  • LRUCache
  • Bitmap內(nèi)存復(fù)用绍刮,壓縮(inSampleSize,inBitmap)
  • StringBuilder(字符串的拼接)

視圖優(yōu)化

1.降低View層級(jí)

  • LinearLayout VS RelativeLayout
  • merge
  • 不必要的背景

2.去掉window默認(rèn)的背景(getWindow().setBackgroundDrawable(null))

去掉不必要的背景(每次添加背景都會(huì)再繪制一次)

ClipRect&QuickReject(尤其在自定義控件時(shí)使用)

ViewStub(控件某些條件才展示)

.9圖用作背景(例如ImageView)

電量消耗

25~30%消耗用在核心功能上

  • 畫圖
  • 布局
  • 動(dòng)畫

剩下的75%左右

  • 上傳統(tǒng)計(jì)數(shù)據(jù)
  • 檢查位置信息
  • 輪訓(xùn)服務(wù)器,拉取廣告信息

網(wǎng)絡(luò)

  • Android網(wǎng)絡(luò)模塊一段時(shí)間一直在運(yùn)行

WakeLock

  • 阻止系統(tǒng)進(jìn)入睡眠狀態(tài)(謹(jǐn)慎使用挨摸、釋放很難)
    AlarmManager里AlarmManager.setInexact()不會(huì)嚴(yán)格按照時(shí)間會(huì)把相鄰的時(shí)間放到一起進(jìn)行
    JobScheduler
    制定幾乎任意一個(gè)場(chǎng)景去喚醒

非即時(shí)的任務(wù)

Battery Stats

Battery Historian

更詳細(xì)的數(shù)據(jù)

網(wǎng)絡(luò)優(yōu)化

  • 何時(shí)請(qǐng)求(是否是即時(shí)請(qǐng)求)
  • 如何請(qǐng)求(一次發(fā)送所有相關(guān)的請(qǐng)求)
    解決辦法 利用WIFI
    預(yù)取數(shù)據(jù)
    避免輪詢服務(wù)器(使用googole的GSM或國內(nèi)第三方的推送服務(wù))
    數(shù)據(jù)壓縮(從減少網(wǎng)絡(luò)請(qǐng)求消耗的時(shí)間孩革,但會(huì)增加一些解析數(shù)據(jù)的時(shí)間,不過是可以接受的)

一些補(bǔ)充

WeakReference與SoftReference
通常用于Cache.如果Cache中的對(duì)象要長(zhǎng)期保存用強(qiáng)引用得运,只是臨時(shí)使用可以用軟引用如:下載圖片到本地膝蜈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市熔掺,隨后出現(xiàn)的幾起案子彬檀,更是在濱河造成了極大的恐慌,老刑警劉巖瞬女,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窍帝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诽偷,警方通過查閱死者的電腦和手機(jī)坤学,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門疯坤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人深浮,你說我怎么就攤上這事压怠。” “怎么了飞苇?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵菌瘫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我布卡,道長(zhǎng)雨让,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任忿等,我火速辦了婚禮栖忠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贸街。我一直安慰自己庵寞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布薛匪。 她就那樣靜靜地躺著捐川,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逸尖。 梳的紋絲不亂的頭發(fā)上属拾,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音冷溶,去河邊找鬼渐白。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逞频,可吹牛的內(nèi)容都是我干的纯衍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼苗胀,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼襟诸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起基协,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤歌亲,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后澜驮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陷揪,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了悍缠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卦绣。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖飞蚓,靈堂內(nèi)的尸體忽然破棺而出滤港,到底是詐尸還是另有隱情,我是刑警寧澤趴拧,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布溅漾,位于F島的核電站,受9級(jí)特大地震影響著榴,放射性物質(zhì)發(fā)生泄漏添履。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一兄渺、第九天 我趴在偏房一處隱蔽的房頂上張望缝龄。 院中可真熱鬧汰现,春花似錦挂谍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗅战,卻和暖如春妄田,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驮捍。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國打工疟呐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人东且。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓启具,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親珊泳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鲁冯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容