1.如何檢測內(nèi)存問題
內(nèi)存泄漏:在基于Java的運行中,內(nèi)存泄漏是一種編程錯誤,它會導致應用程序?qū)σ呀?jīng)不需要再使用對象的引用。所以,無法回收該系統(tǒng)給該對象分配的內(nèi)存。最終導致OOM(OutOfMemoryError 內(nèi)存泄漏) 崩潰。
簡單來說就是:一些對象有著有限的生命周期。當這些對象所要做的事情完成了,我們希望他們會被回收掉。但是如果有一系列對這個對象的引用,那么在我們期待這個對象生命周期結(jié)束的時候被收回的時候,它是不會被回收的。它還會占用內(nèi)存,這就造成了內(nèi)存泄露。持續(xù)累加,內(nèi)存很快被耗盡。
通過工具來檢測是否錯在內(nèi)存泄漏的問題:
1. 通過Android Studio 自帶的工具 Android Profilter 找到MEMORY這個欄目 進行檢測

2.LeakCanary
第一步使用LeakCanary 我們需要先導入兩個依賴
debugImplementation'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

第二步在我們自定義繼承Application類的onCreate()方法里面

如果需要具體的檢查內(nèi)存泄漏的時候:

完成以上操作之后會在我們的手機或者模擬器上生成此應用

LeakCanary會找到并修復 多個內(nèi)存泄漏問題 將OOM崩潰的幾率降低94%。
詳解:
https://blog.csdn.net/qq_20280683/article/details/77964208
https://www.cnblogs.com/fuyaozhishang/p/7753013.html
2.內(nèi)存溢出和泄漏的區(qū)別及常見內(nèi)存問題
1.內(nèi)存泄漏 memory leak
指程序在申請內(nèi)存后,被某個對象一直持有,無法釋放已申請的內(nèi)存空間 一次內(nèi)存泄漏危害可以忽略,但是內(nèi)存泄漏堆積后果是很嚴重的。無論你有多少內(nèi)存,遲早被占光。
內(nèi)存泄漏又分好幾種情況:內(nèi)存泄漏的分類:以發(fā)生的方式來分類,內(nèi)存泄漏可
以分為 4 類:
1. 常發(fā)性內(nèi)存泄漏。
發(fā)生內(nèi)存泄漏的代碼會被多次執(zhí)行到,每次被執(zhí)行的時候都會導致一塊內(nèi)存泄
漏。
2. 偶發(fā)性內(nèi)存泄漏。
發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會發(fā)生。常發(fā)性和偶發(fā)
性是相對的。對于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測試環(huán)境
和測試方法對檢測內(nèi)存泄漏至關(guān)重要。
3. 一次性內(nèi)存泄漏。
發(fā)生內(nèi)存泄漏的代碼只會被執(zhí)行一次,或者由于算法上的缺陷,導致總會有一塊
僅且一塊內(nèi)存發(fā)生泄漏。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒
有釋放該內(nèi)存,所以內(nèi)存泄漏只會發(fā)生一次。
4. 隱式內(nèi)存泄漏。
程序在運行過程中不停的分配內(nèi)存,但是直到結(jié)束的時候才釋放內(nèi)存。嚴格的說
這里并沒有發(fā)生內(nèi)存泄漏,因為最終程序釋放了所有申請的內(nèi)存。但是對于一個
服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內(nèi)存也可能導致最終
耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏
內(nèi)存泄漏出現(xiàn)的一些實例:
1.單例--生命周期
分析:因為單例的生命周期和應用程序一樣,如果單例對象持有了某個不再需要
的對象的引用,(比如 Activity 的 context),那么這個 Activty 在單例沒有被
釋放前將不會被釋放。
解決:我們可以讓單例的引用為 Application 的 context
2.Handler
我們經(jīng)常會在 activity 中這樣使用 handler:
class MyHandler extends Handler{
...
}//使用
MyHandler mHandler=new MyHandler(this);
分析:由于 myHandler 是 Handler 的非靜態(tài)匿名內(nèi)部類的實例,所以它持有外部
類 Activity 的引用,Looper 線程不斷輪詢處理消息,Activity 退出時如果消息隊列
里還有未處理的消息,消息隊列的 Message 持有 mHandler 的引用,mHandler 又
持有 Activity 的引用,所以導致 Activity 無法及時被 GC 回收。從而造成內(nèi)存泄漏
解決方法:
1.創(chuàng)建靜態(tài) Handler 的匿名內(nèi)部類 static class MyHandler extends Handler
2.把對 Handler 持有的對象的使用弱引用 WeakReference context;
3. 在 Activity 銷 毀 時 移 除 消 息 隊 列 中 的 任 務 或 消
息 handler.removeCallbacksAndMessages(null);取消所有的消息的處理
3.非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實例
分析:非靜態(tài)內(nèi)部類可以自由使用外部類的所有變量和方法,非靜態(tài)內(nèi)部類,它
默認持有外部類的引用,此時如果在外部類創(chuàng)建靜態(tài) static 的內(nèi)部類的實例,或
是聲明為 static 靜態(tài)成員變量,這樣就導致內(nèi)部類的生命周期和應用程序一樣長,
導致 Activity 無法正常銷毀。
解決方法:將非靜態(tài)內(nèi)部類轉(zhuǎn)為靜態(tài)內(nèi)部類,這樣就不會隱式持有外部類。
4.線程造成的內(nèi)存泄漏
分析:我們常用的異步任務(如:AsyncTask)和 Runnable 都是匿名內(nèi)部類,所以它
們對當前的 Activity 都有一個隱式引用,若 Activity 銷毀,但是線程的任務還沒有
完成,就會造成 Activity 的 gc 無法回收。
解決方法:
1.使用靜態(tài)內(nèi)部類 將 Runnable 內(nèi)部類、AsyncTask 內(nèi)部類聲明為靜態(tài)。
2.銷毀時取消相應的任務。
5.資源未關(guān)閉
BroadcastReceiver、File、Cursor、Stream、Bitmap 及時關(guān)閉和注銷、否則不會
被回收造成內(nèi)存泄漏。
6.系統(tǒng)服務、監(jiān)聽器未注銷/移除
有一些系統(tǒng)服務或監(jiān)聽器在不需要使用的時候再及時移除或注銷
7.動畫
對于有一些屬性動畫,屬性為無限循環(huán),這時候我們可以在 onStop 中停止動畫。
2.內(nèi)存溢出 out of memory
指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,出現(xiàn) out of memory;
比如申請了一個 integer,但給它存了 long 才能存下的數(shù),那就是內(nèi)存溢出。
內(nèi)存溢出就是你要求分配的內(nèi)存超出了系統(tǒng)能給你的,系統(tǒng)不能滿足需求,于是
產(chǎn)生溢出。
內(nèi)存溢出出現(xiàn)的情況:
1.對象內(nèi)存過大(圖片、Bitmap、XML)造成內(nèi)存超出
2.布局重復加載(比如列表控件 adapter 中沒有復用 view 等)、界面橫豎屏切換。
應用資源過多,來不及加載。
3.還有我們上面介紹的內(nèi)存泄漏,過多的內(nèi)存泄漏,也會導致虛擬機可分配的內(nèi)
存越來越少,這樣也是容易出現(xiàn) OOM。
關(guān)于避免 OOM:
A:減少 OOM 最重要的就是要盡量減少新分配出來的對象占用內(nèi)存的大小,盡
量使用更加輕量的對象。
避免內(nèi)存泄漏,見上面我們總結(jié)的一些情況(比如:善用 static、避免無關(guān)引
用無法釋放、善用 SoftReference/WeakReference/LruCache、謹慎 handler、線程
等、及時關(guān)閉無用服務、監(jiān)聽。)
如果代碼中有大量字符串拼接操作,使用 StringBuilder 代替"+"。
Bitmap 的不當處理極可能造成 OOM,其實很多 OOM 的原因都來源于此,所以
一定要十分重視對 Bitmap 的優(yōu)化。
B:一直說 OOM 的出現(xiàn)是因為應用占用的內(nèi)存(主要是指的 heap)超出了系統(tǒng)
給我們分配內(nèi)存的最大值,那有沒有可能增加系統(tǒng)為我們的 App 分配的內(nèi)存大
小。--->使用 largeHeap,會請求系統(tǒng)為 Dalvik 虛擬機分配更大的內(nèi)存空間。使用
起來也很方便,只需在 manifest 文件 application 節(jié)點加入 android:largeHeap= “ true ” 即 可 。 作 為 驗 證 , 可 以 通 過 打 印 兩 者 的 值 。
ActivityManager.getMemoryClass() 獲 得 應 用 正 常 情 況 下 內(nèi) 存 的 大 小 ,
ActivityManager.getLargeMemoryClass()可以獲得使用 largeHeap 最大的內(nèi)存大小。
但是這個東西需要慎用,不建議使用
3.內(nèi)存優(yōu)化的方案
1.對象引用。強引用、軟引用、弱引用、虛引用四種引用類型,根據(jù)業(yè)務需求合
理使用不同,選擇不同的引用類型。
2. 減少不必要的內(nèi)存開銷。注意自動裝箱,增加內(nèi)存復用,比如有效利用系統(tǒng)自
帶的資源、視圖復用、對象池、Bitmap 對象的復用。
3. 使用最優(yōu)的數(shù)據(jù)類型。比如針對數(shù)據(jù)類容器結(jié)構(gòu),可以使用 ArrayMap 數(shù)據(jù)結(jié)
構(gòu),避免使用枚舉類型,使用緩存 Lrucache 等等。
4. 圖片內(nèi)存優(yōu)化。可以設置位圖規(guī)格,根據(jù)采樣因子做壓縮,用一些圖片緩存方
式對圖片進行管理等等