1、用通俗易懂的講解方式,講解一門技術的實用價值
2、詳細書寫源碼的追蹤,源碼截圖,繪制類的結構圖,盡量詳細地解釋原理的探索過程
3、提供Github 的 可運行的Demo工程,但是我所提供代碼,更多是提供思路,拋磚引玉,請酌情cv
4、集合整理原理探索過程中的一些坑,或者demo的運行過程中的注意事項
5、用gif圖,最直觀地展示demo運行效果創新互聯建站長期為1000多家客戶提供的網站建設服務,團隊從業經驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯網生態環境。為蘄春企業提供專業的成都網站建設、網站制作,蘄春網站改版等技術服務。擁有十余年豐富建站經驗和眾多成功案例,為您定制開發。
如果覺得細節太細,直接跳過看結論即可。本人能力有限,如若發現描述不當之處,歡迎留言批評指正。
學到老活到老,路漫漫其修遠兮。與眾君共勉 !,和我一起當個CV工程師吧,手動滑稽
LMK (LowMemoryKill)機制android底層會在系統內存告急的時候,按照一定規則殺死一些進程來滿足其他進程的內存需要。其中 消耗內存的高低就是其中一項指標,所以,優化app的內存占用,能夠有效降低app被系統殺死的概率。
GC STW機制GC,垃圾回收進程,在GC線程執行任務的時候,會存在一個STW (stop the world)機制,他就會把其他所有線程都掛起。如果GC非常頻繁地調用,那就會導致主線程不流暢,給用戶的感覺就是 卡頓。
內存抖動頻繁引起OOM內存抖動太頻繁,導致大量對象頻繁創建和銷毀,會產生大量不連續的內存空間,如果此時有一個大對象需要申請內存,就有可能申請失敗,導致OOM內存溢出
一句話解釋 內存泄漏長生命周期的對象持有 短生命周期對象的強引用,在 短生命周期對象需要回收的時候發現 不能被回收,視為泄漏
GC回收 可達性分析
GC線程判定 一個對象是不是可以回收,是根據可達性分析算法,計算GcRoot,從GcRoot向下搜索,把GcRoot沒有直接關聯的對象全部作為垃圾來回收。
強軟弱虛四大引用強和虛自不必說。強 最常見,沒有特殊處理的都是強引用(包括,匿名內部類會持有外部類的強引用)。虛引用沒什么用,不予討論。軟引用,用來定義一些還有用,但是不是必須的對象,使用SoftRefrence<T>修飾,在內存緊張的時候,GC回收之后,使用SoftRefrence<T>修飾,如果系統還有足夠的內存可用,那么軟引用關聯的對象就不會被回收。如果不足,則回收軟引用關聯的對象。弱引用(WeakRefrence<T>),比軟引用更弱一些,只要GC觸發,弱引用關聯的對象就會被回收。
注意: 使用軟和弱引用,要判定關聯對象是否為空。
檢測以及處理內存抖動
我們使用s開發,平時我們運行app,一般會點 RunApp,但是還有另一個選擇, 那就是 profileApp, 運行app起來之后,會在as下方看到profile 窗點擊之后,as下方會出現profile,圖中會顯示網絡,內存和cpu使用情況

如果內存的圖中抖動得非常明顯,比如像這樣的心電圖一樣:
那就說明非常明顯存在內存抖動,急需處理: 點擊內存圖形區域之后,就能看到詳細的內存變化情況,以及內存分配情況:
這里有個坑:
如果你從圖形中觀察到,內存走勢平穩,并沒有出現上滿模擬抖動的圖中那么夸張,是不是就不存在內存抖動呢?并不是。因為我們的gc,是在內存不可用的情況下才會去回收內存,如果app占用內存一直比較少,沒有觸及gc的臨界值,那么就不會出現 斷崖式下跌. 那么這樣就觀察不出內存抖動了,怎么辦呢?
解決方法在8.0以下的安卓手機上,在下方的位置上會出現一個Record按鈕(如果是8.0以上,你可以直接用拖拽的方式來截取一段內存record):
點擊它,一段時間之后,再點一下:你就能在下方發現一張表格:
這張表格代表的是,你Record這段時間之內創建的對象,點擊一下第二列 Allocations,對創建的數量進行排序,找出創建次數最多的對象:
然后,點擊排行第一的String之后,會在右方看到
然后點擊其中的一個,又會看到一個新的窗口:
此為止,就找到了 創建對象的 元兇,以這個為線索,找到你們自己包名下的類和方法,確定是我們自己的代碼在不合理地創建對象.
再往后,就是根據各自的業務代碼去做優化了,記住一個宗旨:不要讓代碼干多余的事。如果是我們調用了系統的api導致了不合理地大量對象的創建,那么就要考慮這個系統API為什么會這樣創建對象,有沒有其他方法避免嗎,從業務代碼層來合理使用這個api,實在不行再考慮自定義api或者換個系統api。
在我們做了一次優化之后,再profile運行一次app,再重復上面的過程。以此類推,直到內存抖動達到理想狀態。
優化內存抖動,核心就是防止頻繁創建對象。常見的反面教材就是:循環中創建對象,大量調用的api中創建對象。而優化的主要手段,就是對象復用,常見的手段是:對象池,像是 Handler的Message 單鏈表池,Glide的bitmap池等。
檢測以及處理內存泄漏
經典案例:處理 handler異步任務導致的內存泄漏方法
onDestroy中移除所有的任務
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);//移除所有任務
}MyHandler handler = new MyHandler(this);
private static class MyHandler extends Handler {
WeakReference<Activity> activityWeakReference;
MyHandler(Activity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
//在執行任務的時候,判斷弱引用所關聯的對象是否為空,能在對象已經被回收的情況下
switch (msg.what) {
case 1:
if (activityWeakReference.get() !=null) {
//T0D0
}
}
}
}依然是 profileApp,先用 profile看出內存的變化情況。
答:內存泄漏是精細功夫,不能全盤觀察,只能憑借profile的內存變化來推測。比如,打開app之后內存一路飆升,直到超出app能夠使用的最大內存,app崩潰,,這是最明顯的。又比如,你反復打開關閉某一個界面,發現內存的穩定線( 內存穩定之后,內存占用值)隨著每一次的打開關閉,都在提高,這說明,這一個界面上存在泄漏,有對象無法被回收。
上一章節使用 profile 最多是了解到 哪些對象的創建和回收引起了內存抖動,但是,涉及到泄漏,只通過profile尚且 不能知道是 哪個類持有了希望被回收的對象的強引用. 這里就要借助另外一款工具,他的名字叫做 EclipseMat (自行百度)
先回到剛才的 profile
點一下,然后再點一下,界面會自動跳轉:

點擊上面的保存按鈕,將文件存到本地;
然后:
但是這個文件是無法直接在mat打開的
找到SDK目錄下的要 hprof-conv.exe:
使用cmd命令,對文件進行轉換,命令為:hprof-conv[源文件名][目標文件名]如 hprof-conv1.hprof2.hprof回車
將得到的 2.hprof利用剛才下載的Mat工具打開:
這里有很多指標,但是檢查內存泄漏,我們只需要關注這個直方圖按鈕即可:
這個圖中會列出你dump的這一段內存中的所有對象,包括framework層的,也包括我們自己代碼創建的對象
我模擬了一個經典案例,也就是前面提到的 Handler延時任務導致 Activity不能被釋放,核心代碼如下
public class SecondActivity extends AppComatActivity {
Handler handler = new Handler();
//創建一個強引用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);
//我讓任務永遠在這里
}我就用一個非常普通的方式創建了一個 handler對象,并且用它來執行一段延時任務,只不過,延時任務的延時時間是 Integer的最大值,也就是說,任務要很久以后才會執行。之后,我反復進出這一個 Activity,然后按照上面的方式 dump了一段 hprof,經過 hprof-conv 轉化,然后用 Mat打開:結果如下
我填寫過濾信息: SecondActivity 回車
在我們最終退出SecondActivity之后,內存中依然保留了 18個無用的對象。
那么是不是我們這18個都是泄漏的呢?
不一定
前文講過,只有不合理的強引用,才會導致內存泄漏,所以我們要按照上面的方式排除軟弱虛引用。之后我們能看到下面的界面,把能展開的信息盡數展開
了解 Handler源碼的同志們應該一眼就看明白了, handler引起了內存泄漏,是因為存在不合理地強引用鏈, 上圖中可以看出,最終是callback對象持有了 SecondActivity對象。
我們剛才已經看到了Handler的不合理使用導致了內存泄漏,那么如果在 onDestroy中移除所有的任務
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(token.null):
}
}執行同樣的任務,dump下來的hprof 在mat觸發了GC之后, SecondActivity數量變為了0,內存泄漏解決。
當然還有另一種做法,靜態內部類+弱引用。
ps:靜態內部類是為了防止內部類持有外部類的引用,弱引用是為了在
GC觸發之時,回收掉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); //依舊是那個延時很久的任務 } Runnable runnable = new MyRunnable(this); private static class MyRunnable implements Runnable { //靜態內部類 WeakReference<activity> activityWeakReference //弱引用 MyRunnable(Activity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void run() { } }
但是排除之后,一個都沒有了。
上面的步驟雖然可行,但是如果有很多頁面都需要排查泄漏,那么我們一個一個頁面去點開關閉,整個過程將會非常冗長難受。其實有辦法解決。回到之前的直方圖:
使用方法為:如果你想進行一個操作,你操作前后各dump一個hprof,命名為 before和after, 然后用hprof-conv轉換一下,變為 before 和 `after ,用eclipse mat同時打開這兩個文件,然后切換到after.hprof` ,點擊上圖中的按鈕
它會讓你選擇想要對比的文件,點擊before,然后過濾SecondActivity
這種方式可以在處理泄漏之前,事先排查可能泄露的代碼區域。簡化我們的優化工作。
內存抖動和泄漏優化涉及到Jvm很多知識點,除了我之前列出的幾點之外,還有很多細枝末節。要做好 內存優化,需要扎實的JVM知識基礎。
分享標題:手把手帶你了解內存抖動和泄漏的優化
網頁鏈接:http://www.yijiale78.com/article48/pcsiep.html
成都網站建設公司_創新互聯,為您提供網站設計公司、微信小程序、企業建站、App開發、商城網站、App設計
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯