Android內(nèi)存優(yōu)化一:java垃圾回收機制
Android內(nèi)存優(yōu)化二:內(nèi)存泄漏
Android內(nèi)存優(yōu)化三:內(nèi)存泄漏檢測與監(jiān)控
Android內(nèi)存優(yōu)化四:OOM
Android內(nèi)存優(yōu)化五:Bitmap優(yōu)化
Memory Profiler
Memory Profiler 是 Profiler 中的其中一個版塊,Profiler 是 Android Studio 為我們提供的性能分析工具,使用 Profiler 能分析應用的 CPU、內(nèi)存、網(wǎng)絡以及電量的使用情況。
進入了 Memory Profiler 界面。
點擊 Record 按鈕后,Profiler 會為我們記錄一段時間內(nèi)的內(nèi)存分配情況。

在內(nèi)存分配面板中,通過拖動時間線來查看一段時間內(nèi)的內(nèi)存分配情況
通過搜索類或者報名的方式查看對象的使用情況

使用Memory Profiler 分析內(nèi)存可以查看官網(wǎng):使用內(nèi)存性能分析器查看應用的內(nèi)存使用情況
Memory Analyzer Tool(MAT)
對于內(nèi)存泄漏問題,Memory Profiler 只能提供一個簡單的分析,不能夠確認具體發(fā)生問題的地方。
而 MAT 就可以幫我們做到這一點,它是一款功能強大的 Java 堆內(nèi)存分析工具,可以用于查找內(nèi)存泄漏以及查看內(nèi)存消耗情況。
- 使用 Memory Profiler 的堆轉儲功能,導出 hprof(Heap Profile)文件。
as 生成hprof文件無法被mat識別,需要進行轉換
使用hprof-conv進行轉換,hprof-conv位于sdk\platform-tools
// 前一個為as生成的hprof文件,后一個為轉換后的文件
hprof-conv xxx.hprof xxx.hprof
ps:as導出hprof前最好先gc幾次,可排除一些干擾
- 使用mat打開轉換后的文件

Histogram 可以列出內(nèi)存中的對象,對象的個數(shù)以及大??; Dominator Tree 可以列出那個線程,以及線程下面的那些對象占用的空間; Top consumers 通過圖形列出最大的object; Leak Suspects 通過MA自動分析泄漏的原因。
- Histogram

Shallow Heap就是對象本身占用內(nèi)存的大小,不包含其引用的對象內(nèi)存,實際分析中作用不大。常規(guī)對象(非數(shù)組)的ShallowSize由其成員變量的數(shù)量和類型決定。數(shù)組的shallow size有數(shù)組元素的類型(對象類型、基本類型)和數(shù)組長度決定。對象成員都是些引用,真正的內(nèi)存都在堆上,看起來是一堆原生的byte[], char[], int[],對象本身的內(nèi)存都很小。
Retained Heap值的計算方式是將Retained Set(當該對象被回收時那些將被GC回收的對象集合)中的所有對象大小疊加?;蛘哒f,因為X被釋放,導致其它所有被釋放對象(包括被遞歸釋放的)所占的heap大小。
- 引用鏈

Path To GC Roots -> exclude all phantim/weak/soft etc. references:查看這個對象的GC Root,不包含虛、弱引用、軟引用,剩下的就是強引用。從GC上說,除了強引用外,其他的引用在JVM需要的情況下是都可以 被GC掉的,如果一個對象始終無法被GC,就是因為強引用的存在,從而導致在GC的過程中一直得不到回收,因此就內(nèi)存泄漏了。

List objects -> with incoming references:查看這個對象持有的外部對象引用
List objects -> with outcoming references:查看這個對象被哪些外部對象引用
- OQL:對象查詢語言
使用對象查詢語言可以快速定位發(fā)生泄漏的Activity及Fragment
select * from instanceof android.app.Activity a where a.mDestroyed = true
select * from instanceof androidx.fragment.app.Fragment a where a.mAdded = false

LeakCanary
使用 MAT 來分析內(nèi)存問題,效率比較低,為了能迅速發(fā)現(xiàn)內(nèi)存泄漏,Square 公司基于 MAT 開源了 LeakCanary,LeakCanary 是一個內(nèi)存泄漏檢測框架。
集成LeakCanary后,可以在桌面看到 LeakCanary 用于分析內(nèi)存泄漏的應用。
當發(fā)生泄漏,會為我們生成一個泄漏信息概覽頁,可以看到泄漏引用鏈的詳情。

初始化
// 繼承ContentProvider,在應用啟動時,初始化LeakCanary
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
InternalAppWatcher.install(application)
return true
}
監(jiān)聽
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
// 在Activity執(zhí)行onActivityDestroyed時,觀察它的回收狀態(tài)
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
companion object {
// 通過application.registerActivityLifecycleCallbacks監(jiān)聽所有Activity的生命周期
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
檢測
// #ObjectWatcher
// 在對象可達性發(fā)生更改時,垃圾收集器會將其插入到這個隊列。
private val queue = ReferenceQueue<Any>()
// 受觀察對象的緩存,保存受觀察對象的弱引用
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
// 1\. 觀察對象
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
...
// 創(chuàng)建弱引用,watchedObject 為觀察對象,即activity
val reference =
KeyedWeakReference(watchedObject,..., queue)
// 保存受觀察對象的弱引用
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
// 將可回收的對象從受觀察對象的緩存中移除
// 當對象變?yōu)槿蹩杉?未被強引用),在最終確定或垃圾回收實際發(fā)生之前,會將WeakReferences入隊
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
// 從queue 取出的對象為弱可及,表示即將要回收的對象,即未發(fā)生泄漏情況
// 所以,可以從受觀察對象的緩存中移除它了
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
// 2\. 清理一下已回收的對象,如果對象已被回收,則無需再走下面的流程
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
// 如果已經(jīng)被回收,則不會存在于緩存中
val retainedRef = watchedObjects[key]
if (retainedRef != null) {d
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
// #ObjectWatcher
// 獲取未被回收的對象數(shù)量
val retainedObjectCount: Int
@Synchronized get() {
// 清理一下已回收的對象
removeWeaklyReachableObjects()
return watchedObjects.count { .. }
}
# HeapDumpTrigger
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// 3\. 獲取未被回收的對象數(shù)量
var retainedReferenceCount = objectWatcher.retainedObjectCount
// 4\. 如果有對象未被回收,執(zhí)行一次GC,然后再獲取一次未被回收的對象數(shù)量
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 5\. 判斷是否有泄漏,如果有,再判斷是否需要提示
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
...
// dump 對內(nèi)存
dumpHeap(retainedReferenceCount, retry = true)
}
分析
LeakCanary 會解析 hprof 文件,并且找出導致 GC 無法回收實例的引用鏈,這也就是泄漏蹤跡(Leak Trace)。
泄漏蹤跡也叫最短強引用路徑,這個路徑是 GC Roots 到實例的路徑。
線上監(jiān)控
LeakCanary 存在幾個問題,不同用于線上監(jiān)控功能
監(jiān)控
主動觸發(fā)GC,會造成卡頓
采集
Dump hprof,會造成app凍結
Hprof文件過大
解析
解析耗時過長
解析本身有OOM風險
線上監(jiān)控需要做的,就是解決以上幾個問題。
各大廠都有開發(fā)線上監(jiān)控方案,比如快手的KOOM,美團的Probe,字節(jié)的Liko
KOOM
總結一下幾點:
- 無主動觸發(fā)GC不卡頓
通過無性能損耗的內(nèi)存閾值監(jiān)控來觸發(fā)鏡像采集。將對象是否泄漏的判斷延遲到了解析時
- 高性能鏡像DUMP
利用系統(tǒng)內(nèi)核COW(Copy-on-write,寫時復制)機制,每次dump內(nèi)存鏡像前先暫停虛擬機,然后fork子進程來執(zhí)行dump操作,父進程在fork成功后立刻恢復虛擬機運行,整個過程對于父進程來講總耗時只有幾毫秒,對用戶完全沒有影響。
hprof分析于裁剪
采用邊緣計算的思路,將內(nèi)存鏡像于閑時進行獨立進程單線程本地分析,不過多占用系統(tǒng)運行時資源;分析完即刪除,不占用磁盤空間;分析報告大小只有KB級別,不浪費用戶流量。
針對鏡像回撈需求,對hprof進行運行時hook裁剪,只保留分析OOM必須的數(shù)據(jù)。裁剪還有數(shù)據(jù)脫敏的好處,只保留對分析問題有用的內(nèi)存中類與對象的組織結構,并不上傳真實的業(yè)務數(shù)據(jù),充分保護用戶隱私。