常見的內存問題主要分爲三種:java
指在短期內有大量的對象被建立或者被回收的現象android
危害:gc頻繁,gc時會發生stw,致使卡頓git
指程序在申請內存後,沒法釋放已申請的內存空間github
危害:一次內存泄漏彷佛不會有很大影響,但內存泄漏堆積後的後果就是內存溢出,致使崩潰。即便沒發生內存溢出,佔據較多內存後,會引起gc,致使卡頓數據庫
指程序運行要用到的內存大於能提供的最大內存,程序運行不了的現象markdown
危害:程序沒法運行app
AndroidStudio提供的內存分析工具,經常使用於直觀判斷是否有內存抖動和內存泄漏dom
如圖所示,能夠直觀的看出來內存的波動,從而判斷是否有內存問題ide
Mat能夠用於深度分析內存抖動和內存泄漏工具
使用方法以下:
一、先用Memory Profiler的堆轉儲功能,記錄下一段時間內的內存狀況,存儲爲hprof文件。
二、使用hprof-conv工具,轉換從Memory Profiler保存下來的hprof文件。由於Mat沒法解析直接從Memory Profiler保存下來的文件,可是Android官方提供了轉換工具hprof-conv。hprof-conv在安卓SDK中的platform-tools目錄中。使用命令以下
hprof-conv 舊hprof文件 轉換後的hprof文件
三、使用Mat解析轉換後的hprof文件
點擊下圖該按鈕打開hprof文件
四、Mat的基本功能
查看某個類引用相關
查看GC Root
在dominator_tree中,查找Activity->Path To GC Roots->with all references
在本案例中,結果以下:
結果出了系統類引用外,Activity被單例PluginManager給引用了,形成了泄漏,Activity沒法釋放
LeakCanary是一個自動化的內存泄漏分析工具
使用方法
一、增長依賴
compile 'com.squareup.leakcanary:leakcanary-android:1.6.1'
複製代碼
二、Application中開啓檢測功能
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
複製代碼
三、操做app,若是有泄漏則會通知出如今手機通知欄中,打開後,可看到如下這種內存泄漏的界面
LeakCanary原理
一、RefWatcher.watch() 建立一個 KeyedWeakReference 到要被監控的對象。
二、而後在後臺線程檢查引用是否被清除,若是沒有,調用GC。
三、若是引用仍是未被清除,把 heap 內存 dump 到 APP 對應的文件系統中的一個 .hprof 文件中。
四、在另一個進程中的 HeapAnalyzerService 有一個 HeapAnalyzer 使用HAHA 解析這個文件。
五、得益於惟一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位內存泄露。
六、HeapAnalyzer 計算 到 GC roots 的最短強引用路徑,並肯定是不是泄露。若是是的話,創建致使泄露的引用鏈。
七、引用鏈傳遞到 APP 進程中的 DisplayLeakService, 並以通知的形式展現出來。
判斷對象是否能被回收,是使用弱引用結合引用隊列實現,當一個對象能被回收,則會被加入到關聯的引用隊列中。以此來判斷對象是否泄漏
下面的代碼是參照LeakCanary檢測是否泄露的源碼所寫的一段檢測代碼,基本能夠體現出leakCanary的泄露檢測原理
class RefWatcher {
private val referenceQueue = ReferenceQueue<Any>()
private val retainedKeys = HashSet<String>()
private val gcTrigger = GcTrigger.Default
private val executor = Executors.newSingleThreadExecutor()
/** * 在Activity destroy時,執行判斷是否泄露 */
fun watch(obj : Any) {
val key = UUID.randomUUID().toString()
retainedKeys.add(key)
val reference = KeyedWeakReference(key, obj, referenceQueue)
executor.execute {
//清除被加到隊列中的對象
removeReachableReferences()
//第一次判斷
if(gone(reference)) {
//沒有泄露
} else {
//跑一遍gc,gc的同時會讓線程掛起100ms
gcTrigger.runGc()
//再清除一次被加入ReferenceQueue中的引用
removeReachableReferences()
//再判斷一次
if(gone(reference)) {
//沒有泄露
} else{
//判斷爲泄露
}
}
}
}
/** * 當一個對象能夠被gc回收時,會被加入referenceQueue * 被加入到referenceQueue的引用,則斷定對象不會泄露,從retainsKey中移除 */
private fun removeReachableReferences() {
var ref = referenceQueue.poll() as KeyedWeakReference
while(ref != null) {
retainedKeys.remove(ref.key)
ref = referenceQueue.poll() as KeyedWeakReference
}
}
/** * retainedKeys中不包含的,則斷定爲可回收,不會泄露 */
private fun gone(reference: KeyedWeakReference) : Boolean {
return !retainedKeys.contains(reference.key)
}
}
複製代碼
ResourceCanary是騰訊的APM中檢測內存泄漏的模塊,該模塊實現和leakCanary十分接近,有兩個點不一樣:
一、leakCanary在二次判斷前,執行gc,後線程掛起100秒,由於調用gc後,虛擬機不必定立刻執行gc,但這種判斷方法存在誤判的可能,可能確實沒有gc。ResourceCanary改良了作法,在gc前,設定一個普通可回收的對象的弱引用,將這個引用做爲哨兵,當有gc發生,則這個對象被回收,反之,則這個對象還存活。依靠這個對象判斷虛擬機是否真正執行了gc。
二、leakCanary分析的是完整的hprof文件,而ResourceCanary是先dump完整的hprof文件,後裁剪。比leakCanary多了一步裁剪工做,減少了hprof文件大小
可是,不管是leakCanary仍是ResourceCanary都不適合線上直接使用,由於完整的hprof文件很大,可能高達幾百兆,不適合線上執行。
Tailor是一個內存快照裁剪壓縮工具,經過hook技術在c層write時裁剪文件,不用存完整的hprof文件,避免dump下來完整的hprof文件,可線上使用
該庫的具體實現可查看:https://github.com/bytedance/tailor
StriceMode是谷歌官方出的運行時檢測工具,主要包含虛擬機策略和線程策略,虛擬機策略能夠檢測Activity泄漏、Sql對象泄漏,某個類實例個數是否超出等,檢測結果能夠從log中打印
StrictMode.setVmPolicy(
new StrictMode.VmPolicy.Builder()
//檢測Activity泄漏
.detectActivityLeaks()
//檢測數據庫對象泄漏
.detectLeakedSqlLiteObjects()
//檢測某個類的實例個數
.setClassInstanceLimit(PluginManager.class, 1)
//檢測結果在log中打印,也可選擇若是泄漏就退出應用
.penaltyLog()
.build());
複製代碼
如何發現:使用memory profiler 觀察,內存呈鋸齒狀,忽高忽低
常見緣由:通常是程序頻繁建立對象引發。或者是內存泄露致使了內存不足,再申請頻繁引起gc致使
危害:頻繁gc,致使卡頓
解決方案:一般可利用Memory Profiler觀察內存抖動,查看內存分配較多,實例較多的部分,找出堆棧排查
定義: 內存中存在沒有用的對象回收不了
危害: 致使可用內存逐漸減小,嚴重時可能致使內存溢出
解決: 經過memory profiler 初步排查,可用內存是否逐漸減小,若是存在這種現象則可能有內存泄露。
在as.中使用堆轉儲功能下載hprof文件,經過platform tools中的工具轉換格式後用mat打開分析。
一般找activity,查看存在對象個數,超過一個的不正常,用mat查看它的gc roots引用,找到緣由處理
集合類添加元素後,在使用完後未及時清理集合,致使集合中的元素沒法釋放
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
//o對象已經沒用了,可是objectList還存在,o沒法釋放
o = null;
}
複製代碼
static Context mContext;
複製代碼
若是Activity被靜態變量引用,則Activity會沒法釋放,須要特別注意
單例這種狀況咱們特別容易忽略,須要養成習慣避免泄漏
public class SingleInstanceClass {
private static SingleInstanceClass instance;
private Context mContext;
private SingleInstanceClass(Context context) {
mContext = context;
}
public static SingleInstanceClass getInstance(Context context) {
if (instance == null) {
instance = new SingleInstanceClass(context);
}
return instance;
}
}
複製代碼
這種狀況下,context若是傳的是Activity就會泄漏,應該傳Application的Context
非靜態內部類/匿名類會持有外部類的引用,而靜態內部類不會
這種狀況常常發生在使用Handler的狀況時,Handler被Message引用,而消息能夠延時觸發,好比延時6秒觸發。而在6秒內Activity已經銷燬了。這時Handler還不能釋放,而Handler是非靜態內部類/匿名類狀況下,則會引用Activity,這時會形成泄漏
本文介紹了三種常見的內存問題,6種內存分析和檢測的工具,內存抖動、內存泄漏的常見處理方式,以及Android中多種常見的因爲編碼不規範形成內存泄漏的緣由。實際生產中,內存問題很是隱蔽難以發現,最好是配合線上線下處理,線下使用如leakCanary這種優秀工具檢測,線上經過對重點模塊內存,oom率,gc次數等進行回傳,針對部分用戶進行內存快照回傳自動化分析監控等操做,來持續地以較低成本進行內存優化。