1、用通俗易懂的講解方式,講解一門技術(shù)的實用價值
在濱州等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站設(shè)計制作、成都網(wǎng)站建設(shè) 網(wǎng)站設(shè)計制作按需定制開發(fā),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),品牌網(wǎng)站設(shè)計,網(wǎng)絡(luò)營銷推廣,成都外貿(mào)網(wǎng)站制作,濱州網(wǎng)站建設(shè)費用合理。
2、詳細(xì)書寫源碼的追蹤,源碼截圖,繪制類的結(jié)構(gòu)圖,盡量詳細(xì)地解釋原理的探索過程
3、提供Github 的 可運行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運行過程中的注意事項
5、用gif圖,最直觀地展示demo運行效果
如果覺得細(xì)節(jié)太細(xì),直接跳過看結(jié)論即可。本人能力有限,如若發(fā)現(xiàn)描述不當(dāng)之處,歡迎留言批評指正。
學(xué)到老活到老,路漫漫其修遠(yuǎn)兮。與眾君共勉 !,和我一起當(dāng)個CV工程師吧,手動滑稽
LMK (LowMemoryKill)機(jī)制 android底層會在系統(tǒng)內(nèi)存告急的時候,按照一定規(guī)則殺死一些進(jìn)程來滿足其他進(jìn)程的內(nèi)存需要。其中 消耗內(nèi)存的高低就是其中一項指標(biāo),所以,優(yōu)化app的內(nèi)存占用,能夠有效降低app被系統(tǒng)殺死的概率。
GC STW機(jī)制 GC,垃圾回收進(jìn)程,在GC
線程執(zhí)行任務(wù)的時候,會存在一個STW (stop the world)
機(jī)制,他就會把其他所有線程都掛起。如果GC
非常頻繁地調(diào)用,那就會導(dǎo)致主線程不流暢,給用戶的感覺就是 卡頓。
內(nèi)存抖動頻繁引起OOM 內(nèi)存抖動太頻繁,導(dǎo)致大量對象頻繁創(chuàng)建和銷毀,會產(chǎn)生大量不連續(xù)的內(nèi)存空間,如果此時有一個大對象需要申請內(nèi)存,就有可能申請失敗,導(dǎo)致OOM
內(nèi)存溢出
一句話解釋 內(nèi)存泄漏長 生命周期的對象持有 短生命周期對象的強(qiáng)引用,在 短生命周期對象需要回收的時候發(fā)現(xiàn) 不能被回收,視為泄漏
GC回收 可達(dá)性分析
GC線程判定 一個對象是不是可以回收,是根據(jù)可達(dá)性分析算法,計算GcRoot
,從GcRoot
向下搜索,把GcRoot
沒有直接關(guān)聯(lián)的對象全部作為垃圾來回收。
強(qiáng)軟弱虛四大引用 強(qiáng)和虛自不必說。強(qiáng) 最常見,沒有特殊處理的都是強(qiáng)引用(包括,匿名內(nèi)部類會持有外部類的強(qiáng)引用)。虛引用沒什么用,不予討論。軟引用,用來定義一些還有用,但是不是必須的對象,使用SoftRefrence<T>
修飾,在內(nèi)存緊張的時候,GC回收之后,使用SoftRefrence<T>
修飾,如果系統(tǒng)還有足夠的內(nèi)存可用,那么軟引用關(guān)聯(lián)的對象就不會被回收。如果不足,則回收軟引用關(guān)聯(lián)的對象。弱引用(WeakRefrence<T>
),比軟引用更弱一些,只要GC觸發(fā),弱引用關(guān)聯(lián)的對象就會被回收。
注意: 使用軟和弱引用,要判定關(guān)聯(lián)對象是否為空。
檢測以及處理內(nèi)存抖動
我們使用s開發(fā),平時我們運行app,一般會點 RunApp
,但是還有另一個選擇, 那就是 profileApp
, 運行app起來之后,會在as下方看到profile 窗點擊之后,as下方會出現(xiàn)profile,圖中會顯示網(wǎng)絡(luò),內(nèi)存和cpu使用情況
如果內(nèi)存的圖中抖動得非常明顯,比如像這樣的心電圖一樣:
那就說明非常明顯存在內(nèi)存抖動,急需處理: 點擊內(nèi)存圖形區(qū)域之后,就能看到詳細(xì)的內(nèi)存變化情況,以及內(nèi)存分配情況:
這里有個坑:
如果你從圖形中觀察到,內(nèi)存走勢平穩(wěn),并沒有出現(xiàn)上滿模擬抖動的圖中那么夸張,是不是就不存在內(nèi)存抖動呢?并不是。因為我們的gc,是在內(nèi)存不可用的情況下才會去回收內(nèi)存,如果app占用內(nèi)存一直比較少,沒有觸及gc的臨界值,那么就不會出現(xiàn) 斷崖式下跌. 那么這樣就觀察不出內(nèi)存抖動了,怎么辦呢?
解決方法 在8.0以下的安卓手機(jī)上,在下方的位置上會出現(xiàn)一個Record按鈕(如果是8.0以上,你可以直接用拖拽的方式來截取一段內(nèi)存record):
點擊它,一段時間之后,再點一下:你就能在下方發(fā)現(xiàn)一張表格:
這張表格代表的是,你Record這段時間之內(nèi)創(chuàng)建的對象,點擊一下第二列 Allocations
,對創(chuàng)建的數(shù)量進(jìn)行排序,找出創(chuàng)建次數(shù)最多的對象:
然后,點擊排行第一的String之后,會在右方看到
然后點擊其中的一個,又會看到一個新的窗口:
此為止,就找到了 創(chuàng)建對象 的 元兇,以這個為線索,找到你們自己包名下的類和方法,確定是我們自己的代碼在不合理地創(chuàng)建對象.
再往后,就是根據(jù)各自的業(yè)務(wù)代碼去做優(yōu)化了,記住一個宗旨:不要讓代碼干多余的事。 如果是我們調(diào)用了系統(tǒng)的api導(dǎo)致了不合理地大量對象的創(chuàng)建,那么就要考慮這個系統(tǒng)API為什么會這樣創(chuàng)建對象,有沒有其他方法避免嗎,從業(yè)務(wù)代碼層來合理使用這個api,實在不行再考慮自定義api或者換個系統(tǒng)api。
在我們做了一次優(yōu)化之后,再profile運行一次app,再重復(fù)上面的過程。以此類推,直到內(nèi)存抖動達(dá)到理想狀態(tài)。
優(yōu)化內(nèi)存抖動,核心就是防止頻繁創(chuàng)建對象。常見的反面教材就是:循環(huán)中創(chuàng)建對象,大量調(diào)用的api中創(chuàng)建對象。而優(yōu)化的主要手段,就是對象復(fù)用,常見的手段是:對象池,像是 Handler的Message 單鏈表池,Glide的bitmap池等。
檢測以及處理內(nèi)存泄漏
經(jīng)典案例: 處理 handler異步任務(wù)導(dǎo)致的內(nèi)存泄漏方法
onDestroy
中移除所有的任務(wù)@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);//移除所有任務(wù)
}
MyHandler handler = new MyHandler(this);
private static class MyHandler extends Handler {
WeakReference activityWeakReference;
MyHandler(Activity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
//在執(zhí)行任務(wù)的時候,判斷弱引用所關(guān)聯(lián)的對象是否為空,能在對象已經(jīng)被回收的情況下
switch (msg.what) {
case 1:
if (activityWeakReference.get() !=null) {
//T0D0
}
}
}
}
依然是 profileApp
,先用 profile看出內(nèi)存的變化情況。
答:內(nèi)存泄漏是精細(xì)功夫,不能全盤觀察,只能憑借profile的內(nèi)存變化來推測。比如,打開app之后內(nèi)存一路飆升,直到超出app能夠使用的大內(nèi)存,app崩潰,,這是最明顯的。又比如,你反復(fù)打開關(guān)閉某一個界面,發(fā)現(xiàn)內(nèi)存的穩(wěn)定線( 內(nèi)存穩(wěn)定之后,內(nèi)存占用值)隨著每一次的打開關(guān)閉,都在提高,這說明,這一個界面上存在泄漏,有對象無法被回收。
上一章節(jié)使用 profile
最多是了解到 哪些對象的創(chuàng)建和回收引起了內(nèi)存抖動,但是,涉及到泄漏,只通過profile尚且 不能知道是 哪個類持有了希望被回收的對象的強(qiáng)引用. 這里就要借助另外一款工具,他的名字叫做 EclipseMat
(自行百度)
先回到剛才的 profile
點一下,然后再點一下,界面會自動跳轉(zhuǎn):
點擊上面的保存按鈕,將文件存到本地;
然后:
但是這個文件是無法直接在mat打開的
找到SDK
目錄下的要 hprof-conv.exe
:
使用cmd
命令,對文件進(jìn)行轉(zhuǎn)換,命令為:hprof-conv
[源文件名][目標(biāo)文件名]如 hprof-conv1.hprof2.hprof
回車
將得到的 2.hprof
利用剛才下載的Mat工具打開:
這里有很多指標(biāo),但是檢查內(nèi)存泄漏,我們只需要關(guān)注這個直方圖按鈕即可:
這個圖中會列出你dump的這一段內(nèi)存中的所有對象,包括framework層的,也包括我們自己代碼創(chuàng)建的對象
我模擬了一個經(jīng)典案例,也就是前面提到的 Handler延時任務(wù)導(dǎo)致 Activity不能被釋放,核心代碼如下
public class SecondActivity extends AppComatActivity {
Handler handler = new Handler();
//創(chuàng)建一個強(qiáng)引用Activity的handler對象
@Override
protected void onCreate (Bundle savedInstancestate) {
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_second);
handler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, Integer.MAX_VALUE);
//我讓任務(wù)永遠(yuǎn)在這里
}
我就用一個非常普通的方式創(chuàng)建了一個 handler
對象,并且用它來執(zhí)行一段延時任務(wù),只不過,延時任務(wù)的延時時間是 Integer
的大值,也就是說,任務(wù)要很久以后才會執(zhí)行。之后,我反復(fù)進(jìn)出這一個 Activity
,然后按照上面的方式 dump
了一段 hprof
,經(jīng)過 hprof-conv
轉(zhuǎn)化,然后用 Mat
打開:結(jié)果如下
我填寫過濾信息: SecondActivity
回車
在我們最終退出SecondActivity
之后,內(nèi)存中依然保留了 18個無用的對象。
那么是不是我們這18個都是泄漏的呢?
不一定
前文講過,只有不合理的強(qiáng)引用,才會導(dǎo)致內(nèi)存泄漏,所以我們要按照上面的方式排除軟弱虛引用。之后我們能看到下面的界面,把能展開的信息盡數(shù)展開
了解 Handler
源碼的同志們應(yīng)該一眼就看明白了, handler
引起了內(nèi)存泄漏,是因為存在不合理地強(qiáng)引用鏈, 上圖中可以看出,最終是callback
對象持有了 SecondActivity
對象。
我們剛才已經(jīng)看到了Handler的不合理使用導(dǎo)致了內(nèi)存泄漏,那么如果在 onDestroy
中移除所有的任務(wù)
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(token.null):
}
}
執(zhí)行同樣的任務(wù),dump
下來的hprof
在mat觸發(fā)了GC
之后, SecondActivity
數(shù)量變?yōu)榱?,內(nèi)存泄漏解決。
當(dāng)然還有另一種做法,靜態(tài)內(nèi)部類+弱引用。
ps: 靜態(tài)內(nèi)部類是為了防止內(nèi)部類持有外部類的引用,弱引用是為了在
GC
觸發(fā)之時,回收掉WeakRefrence
中的對象。public class secondActivity extends AppCompatActivity { Handler handler = new Handler(): @Override protected void onCreate(Bundle saveInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); handler.postDelayed(runnable, Integer.MAX_VALUE); //依舊是那個延時很久的任務(wù) } Runnable runnable = new MyRunnable(this); private static class MyRunnable implements Runnable { //靜態(tài)內(nèi)部類 WeakReference<activity> activityWeakReference //弱引用 MyRunnable(Activity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void run() { } }
但是排除之后,一個都沒有了。
上面的步驟雖然可行,但是如果有很多頁面都需要排查泄漏,那么我們一個一個頁面去點開關(guān)閉,整個過程將會非常冗長難受。其實有辦法解決?;氐街暗闹狈綀D:
使用方法為:如果你想進(jìn)行一個操作,你操作前后各dump一個hprof
,命名為 before和after, 然后用hprof-conv
轉(zhuǎn)換一下,變?yōu)?before 和 `after ,用
eclipse mat同時打開這兩個文件,然后切換到
after.hprof` ,點擊上圖中的按鈕
它會讓你選擇想要對比的文件,點擊before,然后過濾SecondActivity
這種方式可以在處理泄漏之前,事先排查可能泄露的代碼區(qū)域。簡化我們的優(yōu)化工作。
內(nèi)存抖動和泄漏優(yōu)化涉及到Jvm很多知識點,除了我之前列出的幾點之外,還有很多細(xì)枝末節(jié)。要做好 內(nèi)存優(yōu)化,需要扎實的JVM知識基礎(chǔ)。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)cdcxhl.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。