[TOC]
## Memory Profiler
`Memory Profiler`是`Profiler` 中的其中一個版塊,`Profiler` 是 `Android Studio` 為我們提供的性能分析工具,使用 `Profiler` 能分析應用的 `CPU`、內存、網絡以及電量的使用情況。
使用`Memory`可以檢測以下功能
1.查看內存曲線及內存占用情況
2.可以定位是否存在內存抖動問題
3.堆轉儲(`Dump Java Heap`)可檢測出內存泄漏的對象

關于`Memory Profiler`的具體使用就不在此綴述了,想要了解的可參考:[什么是 Memory Profiler?](https://juejin.cn/post/6844903897958449166#heading-48 "https://juejin.cn/post/6844903897958449166#heading-48")
* **Java**:從 Java 或 Kotlin 代碼分配的對象內存。
* **Native**:從 C 或 C++ 代碼分配的對象內存。
即使您的應用中不使用 C++,您也可能會看到此處使用的一些原生內存,因為 Android 框架使用原生內存代表您處理各種任務,如處理圖像資源和其他圖形時,即使您編寫的代碼采用 Java 或 Kotlin 語言。
* **Graphics**:圖形緩沖區隊列向屏幕顯示像素(包括 GL 表面、GL 紋理等等)所使用的內存。 (請注意,這是與 CPU 共享的內存,不是 GPU 專用內存。)
* **Stack**: 您的應用中的原生堆棧和 Java 堆棧使用的內存。 這通常與您的應用運行多少線程有關。
* **Code**:您的應用用于處理代碼和資源(如 dex 字節碼、已優化或已編譯的 dex 碼、.so 庫和字體)的內存。
* **Other**:您的應用使用的系統不確定如何分類的內存。
## showmap
這對于我們來說非常有用,可以確定進程中哪些庫占用內存比較多
~~~
dumpsys meminfo com.sangfor.pocket
ps | grep com.sangfor.pocket
su
showmap [pid]
~~~
## Memory Analyzer Tool
`MAT`工具可以幫助開發者定位導致內存泄漏的對象,以及發現大的內存對象,然后解決內存泄漏并通過優化內存對象,以達到減少內存消耗的目的。
比起`Memory Profiler`,`MAT`使用起來更加麻煩,同時現在`Memory Profiler`功能也越來越強大了,所以我現在已經很少使用`MAT`了
如果想要更多地了解`MAT`,也可以參考:[什么是Memory Analyzer Tool](https://juejin.cn/post/6844903897958449166#heading-52 "https://juejin.cn/post/6844903897958449166#heading-52")
## LeakCanary
eakCanary是一個內存泄漏檢測的框架,默認只會檢測Activity的泄漏,如果需要檢測其他類,可以使用LeakCanary.install返回的RefWatcher,調用RefWatcher.watch(obj)就可以觀測obj對象是否出現泄漏。
### 基本原理
LeakCanary 采用的是 Reference + ReferenceQueue來檢測一個對象是否被回收,大概需要幾個步驟:
* 創建一個引用隊列 queue
* 創建 Refrence 對象,并關聯引用隊列 queue
* 在 reference 被回收的時候,refrence 會被添加到 queue 中
### 基本流程

#### 檢測泄露
觀察對象:`Activity`,`Fragment`,`RootView`,`Service
1. 傳入的觀察對象都會被存儲在`watchedObjects`中
2. 會為每個`watchedObject`生成一個`KeyedWeakReference`弱引用對象并與一個`queue`關聯,當對象被回收時,該弱引用對象將進入`queue`當中
3. 在檢測過程中,我們會調用多次`removeWeaklyReachableObjects`,將已回收對象從`watchedObjects`中移除
4. 如果`watchedObjects`中沒有移除對象,證明它沒有被回收,那么就會調用`moveToRetained`
#### 觸發堆快照,生成`hprof`文件
1. 如果`retainedObjectCount`數量大于0,則進行一次`GC`,避免額外的`Dump`
2. 默認情況下,如果`retainedReferenceCount<5`,不會進行`Dump`,節省資源
3. 如果兩次`Dump`之間時間少于60s,也會直接返回,避免頻繁`Dump`
4. 調用`heapDumper.dumpHeap()`進行真正的`Dump`操作
5. `Dump`之后,要刪除已經處理過了的引用
6. 調用`HeapAnalyzerService.runAnalysis`對結果進行分析
分析`hprof`文件的工作主要是在`HeapAnalyzerService`類中完成的
關于`Hprof`文件的解析細節,就需要牽扯到`Hprof`二進制文件協議,通過閱讀協議文檔,`hprof`的二進制文件結構大概如下:

解析流程如下所示:

簡要說下流程:
1.解析文件頭信息,得到解析開始位置
2.根據頭信息創建`Hprof`文件對象
3.構建內存索引
4.使用`hprof`對象和索引構建`Graph`對象
5.查找可能泄漏的對象與`GCRoot`間的引用鏈來判斷是否存在泄漏(使用廣度優先算法在`Graph`中查找)
### 為什么`LeakCanary`不能用于線上?
1. 每次內存泄漏以后,都會生成一個`.hprof`文件,然后解析,并將結果寫入`.hprof.result`。增加手機負擔,引起手機卡頓等問題。
2. 多次調用`GC`,可能會對線上性能產生影響
3. 同樣的泄漏問題,會重復生成 `.hprof` 文件,重復分析并寫入磁盤。
4.`. hprof`文件較大,信息回撈成問題。
了解了這些問題,我們可以嘗試提出一些解決方案:
1. 可以根據手機信息來設定一個內存閾值 `M` ,當已使用內存小于 `M` 時,如果此時有內存泄漏,只將泄漏對象的信息放入內存當中保存,不生成`.hprof`文件。當已使用大于 `M` 時,生成`.hprof`文件
2. 當引用鏈路相同時,可根據實際情況去重。
3. 不直接回撈`.hprof`文件,可以選擇回撈分析的結果
4. 可以嘗試將已泄漏對象存儲在數據庫中,一個用戶同一個泄漏只檢測一次,減少對用戶的影響
## ResourceCanary
### LeakCanary的缺陷
LeakCanary分析可見其對 Activity 是否泄漏的判斷依賴VM會將可回收的對象加入 WeakReference 關聯的 ReferenceQueue 這一特性,Matrix發現這中做法在個別系統上可能存在誤報,原因大致如下:
* VM 并沒有提供強制觸發 GC 的 API ,通過 System.gc()或 Runtime.getRuntime().gc()只能“建議”系統進行 GC ,如果系統忽略了我們的 GC 請求,可回收的對象就不會被加入 ReferenceQueue
* 將可回收對象加入 ReferenceQueue 需要等待一段時間,LeakCanary 采用延時 100ms 的做法加以規避,但似乎并不絕對管用
* 監測邏輯是異步的,如果判斷 Activity 是否可回收時某個 Activity 正好還被某個方法的局部變量持有,就會引起誤判
* 若反復進入泄漏的 Activity ,LeakCanary 會重復提示該 Activity 已泄漏
### ResourceCanary的改進
對此做了以下改進:
* 增加一個一定能被回收的“哨兵”對象,用來確認系統確實進行了GC
* 直接通過WeakReference.get()來判斷對象是否已被回收,避免因延遲導致誤判
* 若發現某個Activity無法被回收,再重復判斷3次,且要求從該Activity被記錄起有2個以上的Activity被創建才認為是泄漏,以防在判斷時該Activity被局部變量持有導致誤判
* 對已判斷為泄漏的Activity,記錄其類名,避免重復提示該Activity已泄漏
### 修復系統潛在內存泄漏
Matrix ResourceCanary 還針對常見的系統內存泄漏做了針對性的修改。
[InputMethodManager 導致的泄漏](https://www.jianshu.com/p/62e498075e56)
[Drawable泄露](https://www.jianshu.com/p/914888735ce5)
[【帶著問題學】關于LeakCanary2.0你應該知道的知識點](https://juejin.cn/post/6968084138125590541)
[探索 Android 內存優化方法](https://juejin.cn/post/6844903897958449166)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺