Android App內(nèi)存優(yōu)化簡(jiǎn)單說(shuō)明

說(shuō)明

本文是學(xué)習(xí)內(nèi)存優(yōu)化時(shí)個(gè)人的總結(jié)如捅,由于本人是剛開始接觸Android的性能優(yōu)化方面的知識(shí)秆吵,肯定有很多知識(shí)點(diǎn)上的不足和錯(cuò)漏虎囚,請(qǐng)各位諒解角塑。

App內(nèi)存組成以及限制

Android 給每個(gè) App 分配一個(gè) VM ,讓App運(yùn)行在 dalvik 上淘讥,這樣即使 App 崩潰也不會(huì)影響到系統(tǒng)圃伶。系統(tǒng)給 VM 分配了一定的內(nèi)存大小, App 可以申請(qǐng)使用的內(nèi)存大小不能超過(guò)此硬性邏輯限制,就算物理內(nèi)存富余窒朋,如果應(yīng)用超出 VM 最大內(nèi)存搀罢,就會(huì)出現(xiàn)內(nèi)存溢出 crash 。由程序控制操作的內(nèi)存空間在 heap 上侥猩,分 java heapsize 和 native heapsize
Java申請(qǐng)的內(nèi)存在 vm heap 上榔至,所以如果 java 申請(qǐng)的內(nèi)存大小超過(guò) VM 的邏輯內(nèi)存限制,就會(huì)出現(xiàn)內(nèi)存溢出的異常。native層內(nèi)存申請(qǐng)不受其限制, native 層受 native process 對(duì)內(nèi)存大小的限制欺劳。那么如何查看系統(tǒng)對(duì)APP的內(nèi)存限制呢唧取?
(1)如果你的手機(jī)root過(guò),我們可以通過(guò) adb shell 在 命令行窗口查看划提,命令如下:
adb shell cat /system/build.prop

image.png

這里主要關(guān)注三個(gè)屬性即可:
1.heapstartsize:App啟動(dòng)的初始分配內(nèi)存
2.heapgrowthlimit:APP能夠分配到的最大限制
3.heapsize:開啟largeHeap=‘true’的最大限制
作為應(yīng)用的開發(fā)者枫弟,這幾個(gè)值我們是無(wú)法改變的(root過(guò)或者手機(jī)系統(tǒng)開發(fā)者除外),我呢只需要知道有這么幾個(gè)值即可鹏往。
(2)通過(guò)代碼獲取

ActivityManager activityManager =(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();//以m為單位

Android內(nèi)存分配與回收機(jī)制

內(nèi)存分配
Android的Heap空間是一個(gè) Generational Heap Memory 的模型淡诗,最近分配的對(duì)象會(huì)存放在 Young
Generation 區(qū)域,當(dāng)一個(gè)對(duì)象在這個(gè)區(qū)域停留的時(shí)間達(dá)到一定程度伊履,它會(huì)被移動(dòng)到 Old
Generation 韩容,最后累積一定時(shí)間再移動(dòng)到 Permanent Generation 區(qū)域。


image.png

1湾碎、Young Generation(新生代)
由一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)組成宙攻,程序中生成的大部分新的對(duì)象都在Eden區(qū)中,當(dāng)Eden區(qū)滿時(shí)介褥,還存活的對(duì)象將被復(fù)制到其中一個(gè)Survivor區(qū),當(dāng)次Survivor區(qū)滿時(shí)递惋,此區(qū)存活的對(duì)象又被復(fù)制到另一個(gè)Survivor區(qū)柔滔,當(dāng)這個(gè)Survivor區(qū)也滿時(shí),會(huì)將其中存活的對(duì)象復(fù)制到年老代萍虽。
2睛廊、Old Generation(老年代)
一般情況下,年老代中的對(duì)象生命周期都比較長(zhǎng)杉编。
3超全、Permanent Generation(持久代)
用于存放靜態(tài)的類和方法,持久代對(duì)垃圾回收沒(méi)有顯著影響邓馒。

總結(jié):內(nèi)存對(duì)象的處理過(guò)程如下:
1嘶朱、對(duì)象創(chuàng)建后在Eden區(qū)。
2光酣、執(zhí)行GC后疏遏,如果對(duì)象仍然存活,則復(fù)制到S0區(qū)。
3财异、當(dāng)S0區(qū)滿時(shí)倘零,該區(qū)域存活對(duì)象將復(fù)制到S1區(qū),然后S0清空戳寸,接下來(lái)S0和S1角色互換呈驶。
4、當(dāng)?shù)?步達(dá)到一定次數(shù)(系統(tǒng)版本不同會(huì)有差異)后疫鹊,存活對(duì)象將被復(fù)制到Old Generation俐东。
5、當(dāng)這個(gè)對(duì)象在Old Generation區(qū)域停留的時(shí)間達(dá)到一定程度時(shí)订晌,它會(huì)被移動(dòng)到Old
Generation虏辫,最后累積一定時(shí)間再移動(dòng)到Permanent Generation區(qū)域。

系統(tǒng)在Young Generation锈拨、Old Generation上采用不同的回收機(jī)制砌庄。每一個(gè)Generation的內(nèi)存區(qū)域都
有固定的大小。隨著新的對(duì)象陸續(xù)被分配到此區(qū)域奕枢,當(dāng)對(duì)象總的大小臨近這一級(jí)別內(nèi)存區(qū)域的閾值時(shí)娄昆,
會(huì)觸發(fā)GC操作,以便騰出空間來(lái)存放其他新的對(duì)象缝彬。
執(zhí)行GC占用的時(shí)間與Generation和Generation中的對(duì)象數(shù)量有關(guān):
Young Generation < Old Generation < Permanent Generation
Gener中的對(duì)象數(shù)量與執(zhí)行時(shí)間成正比萌焰。

4、Young Generation GC
由于其對(duì)象存活時(shí)間短谷浅,因此基于Copying算法(掃描出存活的對(duì)象扒俯,并復(fù)制到一塊新的完全未使用的控件中)來(lái)回收。新生代采用空閑指針的方式來(lái)控制GC觸發(fā)一疯,指針保持最后一個(gè)分配的對(duì)象在Young Generation區(qū)間的位置撼玄,當(dāng)有新的對(duì)象要分配內(nèi)存時(shí),用于檢查空間是否足夠墩邀,不夠就觸發(fā)GC掌猛。
5、Old Generation GC
由于其對(duì)象存活時(shí)間較長(zhǎng)眉睹,比較穩(wěn)定荔茬,因此采用Mark(標(biāo)記)算法(掃描出存活的對(duì)象,然后再回收未被標(biāo)記的對(duì)象竹海,回收后對(duì)空出的空間要么合并慕蔚,要么標(biāo)記出來(lái)便于下次分配,以減少內(nèi)存碎片帶來(lái)的效率損耗)來(lái)回收站削。

可回收對(duì)象的判定

可達(dá)性算法:
從GC Roots(每種具體實(shí)現(xiàn)對(duì)GC Roots有不同的定義)作為起點(diǎn)坊萝,向下搜索它們引用的對(duì)象,可以生成一棵引用樹,樹的節(jié)點(diǎn)視為可達(dá)對(duì)象十偶,反之視為不可達(dá)菩鲜。


image.png

Java定義的GC Roots對(duì)象:
虛擬機(jī)棧(幀棧中的本地變量表)中引用的對(duì)象。
方法區(qū)中靜態(tài)屬性引用的對(duì)象惦积。
方法區(qū)中常量引用的對(duì)象接校。
本地方法棧中JNI引用的對(duì)象。


image.png

GC類型

kGcCauseForAlloc:分配內(nèi)存不夠引起的GC狮崩,會(huì)Stop World蛛勉。由于是并發(fā)GC,其它線程都會(huì)停止睦柴,直到GC完成诽凌。
kGcCauseBackground:內(nèi)存達(dá)到一定閾值觸發(fā)的GC,由于是一個(gè)后臺(tái)GC坦敌,所以不會(huì)引起Stop World侣诵。
kGcCauseExplicit:顯示調(diào)用時(shí)進(jìn)行的GC,當(dāng)ART打開這個(gè)選項(xiàng)時(shí)狱窘,使用System.gc時(shí)會(huì)進(jìn)行GC杜顺。

GC算法

1.標(biāo)記清除算法
分為兩步
標(biāo)價(jià): 標(biāo)記的過(guò)程其實(shí)就是,遍歷所有的GC Roots蘸炸,然后將所有的 GC Roots可達(dá)的對(duì)象標(biāo)記為存活的對(duì)象躬络。
清除:清除的過(guò)程將遍歷堆中所有的對(duì)象中沒(méi)有標(biāo)記的對(duì)象全部清除掉

image.png

特點(diǎn):
(1)掃描兩次
(2)位置不連續(xù),存在碎片
(3)兩遍掃描

2.復(fù)制算法
描述:
(1)復(fù)制算法將內(nèi)存劃分為兩個(gè)區(qū)間搭儒,在任意時(shí)間點(diǎn)穷当,所有動(dòng)態(tài)分配的對(duì)象都只能分配在其中一個(gè)區(qū)間(稱為活動(dòng)區(qū)間),而另外一個(gè)區(qū)間(稱為空閑區(qū)間)則是空閑的仗嗦。
(2)當(dāng)有效內(nèi)存空間耗盡時(shí)膘滨,JVM將暫停程序運(yùn)行,開啟復(fù)制算法GC線程稀拐。接下來(lái)GC線程會(huì)將活動(dòng)區(qū)間內(nèi)的存活對(duì)象,全部復(fù)制到空閑區(qū)間丹弱,且嚴(yán)格按照內(nèi)存地址依次排列德撬,與此同時(shí),GC線程將更新存活對(duì)象的內(nèi)存引用地址指向新的內(nèi)存地址躲胳。
(3)此時(shí)蜓洪,空閑區(qū)間已經(jīng)與活動(dòng)區(qū)間交換,而垃圾對(duì)象現(xiàn)在已經(jīng)全部留在了原來(lái)的活動(dòng)區(qū)間坯苹,也就是現(xiàn)在的空閑區(qū)間隆檀。事實(shí)上,在活動(dòng)區(qū)間轉(zhuǎn)換為空間區(qū)間的同時(shí),垃圾對(duì)象已經(jīng)被一次性全部回收恐仑。
特點(diǎn):
(1)實(shí)現(xiàn)簡(jiǎn)單泉坐,運(yùn)行高效
(2)空間利用率只有一半
(3)沒(méi)有碎片

image.png

3.標(biāo)記整理算法
描述:
y與標(biāo)記/清除算法類似,分為兩步
(1)標(biāo)記:它的第一個(gè)階段與標(biāo)記/清除算法是一模一樣的裳仆,均是遍歷GC Roots腕让,然后將存活的對(duì)象標(biāo)記。
(2)整理:移動(dòng)所有存活的對(duì)象歧斟,且按照內(nèi)存地址次序依次排列纯丸,然后將末端內(nèi)存地址以后的內(nèi)存全部回收。因此静袖,第二階段才稱為整理階段觉鼻。
特點(diǎn):
(1)沒(méi)有內(nèi)存碎片
(2)效率偏低
(3)兩遍掃描,指針需要移動(dòng)


image.png

Android低內(nèi)存殺進(jìn)程機(jī)制

Anroid基于進(jìn)程中運(yùn)行的組件及其狀態(tài)規(guī)定了默認(rèn)的五個(gè)回收優(yōu)先級(jí):


image.png

Empty process(空進(jìn)程)
Background process(后臺(tái)進(jìn)程)
Service process(服務(wù)進(jìn)程)
Visible process(可見(jiàn)進(jìn)程)
Foreground process(前臺(tái)進(jìn)程)

系統(tǒng)需要進(jìn)行內(nèi)存回收時(shí)最先回收空進(jìn)程,然后是后臺(tái)進(jìn)程队橙,以此類推最后才會(huì)回收前臺(tái)進(jìn)程(一般情況
下前臺(tái)進(jìn)程就是與用戶交互的進(jìn)程了,如果連前臺(tái)進(jìn)程都需要回收那么此時(shí)系統(tǒng)幾乎不可用了)坠陈。


image.png

ActivityManagerService 會(huì)對(duì)所有進(jìn)程進(jìn)行評(píng)分(存放在變量adj中),然后再講這個(gè)評(píng)分更新到內(nèi)核喘帚,由內(nèi)核去完成真正的內(nèi)存回收( lowmemorykiller , Oom_killer )畅姊。這里只是大概的流程,中間過(guò)程還是很復(fù)雜的

什么是OOM

OOM(OutOfMemoryError)內(nèi)存溢出錯(cuò)誤吹由,在常見(jiàn)的Crash疑難排行榜上若未,OOM絕對(duì)可以名列前茅并且經(jīng)久不衰。因?yàn)樗l(fā)生時(shí)的Crash堆棧信息往往不是導(dǎo)致問(wèn)題的根本原因倾鲫,而只是壓死駱駝的最后一根稻草粗合。


image.png

OOM分類

image.png

內(nèi)存泄露的解決方法

1.常見(jiàn)的分析工具
(1)MAT
(2)Memory Profile
(3)LeakCanary

Memory Profile檢測(cè)內(nèi)存泄露
首先我們寫一個(gè)測(cè)試Demo,在MainActivity中打開SecondActivity乌昔,在伴隨對(duì)象中持有SeconnActivity的實(shí)例隙疚,然后關(guān)閉SecondActivity的實(shí)例。secndActivity的到代碼如下:

class SecondActivity : AppCompatActivity() {

    private lateinit var mButton: AppCompatButton
    private var mWeakRef: WeakReference<String>? = null


    companion object {
        var context123: Context? = null
        var weakReferenceObj: WeakReference<String>? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        context123 = this
        setContentView(R.layout.activity_second)
        mButton = findViewById(R.id.btn_click)
        mWeakRef = WeakReference("lala")
        weakReferenceObj = mWeakRef
        mButton.setOnClickListener {
            finish()
        }
    }
}

首先我們用Memory Proflile 分析該內(nèi)存泄露
步驟如下
(1)運(yùn)行項(xiàng)目
(2)點(diǎn)擊Profile進(jìn)入分析界面磕道,點(diǎn)擊左上角的+添加分析的項(xiàng)目


image.png

image.png

(3)綁定成功回進(jìn)入分析界面供屉,點(diǎn)擊memory,進(jìn)入內(nèi)存分析


image.png

(4)打開SecondActivity頁(yè)面后在關(guān)閉該頁(yè)面溺蕉,點(diǎn)擊Capture head dump伶丐,再按下record捕捉內(nèi)存視圖
image.png

(5)選擇show activity/fragment Leaks 既可以看到發(fā)生內(nèi)存泄露的相關(guān)activity或fragment
image.png

(6)點(diǎn)擊下方的Instance List的相關(guān)實(shí)例,點(diǎn)擊Reference便可以看到相關(guān)對(duì)象的持有情況疯特。
image.png

(7)分析相關(guān)的引用持有情況哗魂,這里可以看到,我們自己寫的mContext123漓雅,分析該mContext何時(shí)被賦值的

 companion object {
        var context123: Context? = null
        var weakReferenceObj: WeakReference<String>? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        context123 = this
        setContentView(R.layout.activity_second)
        mButton = findViewById(R.id.btn_click)
        mWeakRef = WeakReference("lala")
        weakReferenceObj = mWeakRef
        mButton.setOnClickListener {
            finish()
        }
    }

發(fā)現(xiàn)該mContext123持有了一個(gè)SecondActivity的實(shí)例录别,當(dāng)該SecondActivity對(duì)象想要執(zhí)行銷毀時(shí)朽色,因?yàn)楸籱Context123持有而無(wú)法被銷毀,從而造成了內(nèi)存泄露组题。


image.png

至此Meomery Profile的內(nèi)存泄露檢測(cè)說(shuō)明完畢

LeakCanary檢測(cè)內(nèi)存泄露
(1)首先引入LeakCanary

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

(2)然后運(yùn)行項(xiàng)目葫男,LeakCanary會(huì)自動(dòng)檢測(cè)內(nèi)存泄露的點(diǎn),如果檢測(cè)到會(huì)在通知欄顯示一條通知


image.png

(3)點(diǎn)擊后進(jìn)入Leack Canary 可以看到發(fā)生了泄露


image.png

(4)點(diǎn)擊該條目往踢,可以看到發(fā)生泄露的點(diǎn)腾誉,可以看到與Memory Profile找的泄漏點(diǎn)一致。
image.png

至此峻呕,LeakCanary檢測(cè)內(nèi)存泄露的說(shuō)明講解完畢

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末利职,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子瘦癌,更是在濱河造成了極大的恐慌猪贪,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讯私,死亡現(xiàn)場(chǎng)離奇詭異热押,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斤寇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門桶癣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娘锁,你說(shuō)我怎么就攤上這事牙寞。” “怎么了莫秆?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵间雀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我镊屎,道長(zhǎng)惹挟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任缝驳,我火速辦了婚禮连锯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘用狱。我一直安慰自己萎庭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布齿拂。 她就那樣靜靜地躺著,像睡著了一般肴敛。 火紅的嫁衣襯著肌膚如雪署海。 梳的紋絲不亂的頭發(fā)上吗购,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音砸狞,去河邊找鬼捻勉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛刀森,可吹牛的內(nèi)容都是我干的踱启。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼研底,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼埠偿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起榜晦,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤冠蒋,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后乾胶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抖剿,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年识窿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斩郎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喻频,死狀恐怖缩宜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情半抱,我是刑警寧澤脓恕,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站窿侈,受9級(jí)特大地震影響炼幔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜史简,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一乃秀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧圆兵,春花似錦跺讯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至超凳,卻和暖如春愈污,著一層夾襖步出監(jiān)牢的瞬間耀态,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工暂雹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留首装,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓杭跪,卻偏偏與公主長(zhǎng)得像仙逻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子涧尿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354