内存泄漏之KOOM
文章目錄
- 資料
- Hprof文件分析
- Android中的Hprof
- 管理應用內存
- 繼承ComponentCallbacks2接口,并onTrimMemory中回調
- 查看您應該使用多少內存
- 怎么引發內存泄漏
- Android中的泄漏對象
- 泄漏的形式
- Profiler的使用
- 內存性能分析器Profiler概覽
- Heap分區
- Instance View
- Depth
- Native Size
- Shallow Size
- Retained Size
- Reference
- KOOM
- KOOM的整體依賴圖
- 無主動出發GC不卡頓
- 啟動服務之前創建的Intent傳參
- HeapAnalysisService
- HeapGraph
- ForkJvmHeapDumper
- JavaHeap(內存使用信息)
- ProcStatus
- MemInfo
- hprofStringCache
- LeakCanary
- LeakCanary#install
- ActivityRefWatcher
- RefWatcher
- RefWatcher#ensureGone
- HeapDumper#dumpHeap
- HeapAnalyzerService
- HeapAnalyzer#checkForleak
- AbstractAnalysisResultService
- 09.DisplayLeakService
資料
Hprof Agent
KOOM
Hprof
android中hprof文件分析
快照技術(COW與ROW技術)
Android內存泄漏簡介
關于Hprof的方方面面
內存性能分析器中的泄漏檢測
分析應用性能
使用內存性能分析器查看應用的內存使用情況
管理應用內存
Android開發之——Profiler-內存分析
/proc/self/status講解
Linux EUID,SUID,RUID簡單理解
Linux:/proc/meminfo參數詳細解釋
讀取Android設備的內存數據- USS,PSS,RSS
Debug
Linux內存那些事 – VSS、RSS、PSS、USS
Linux 進程內存使用統計VSS RSS PSS USSS
Andorid9.0和10.0下的的libart.so
深入理解 Android 之 LeakCanary 源碼解析
Hprof文件分析
Hprof最初是由J2SE支持的一種二進制堆轉儲格式,hprof文件保存了當前java堆上所有的內存使用信息,能夠完整的反映虛擬機當前的內存狀態。
Hprof文件由FixedHead和一系列的Record組成,Record包含字符串信息、類信息、棧信息、GcRoot信息、對象信息。每個Record都是由1個字節的Tag、4個字節的Time、4個字節的Length和Body組成,Tag表示該Record的類型,Body部分為該Record的內容,長度為Length。
Android中的Hprof
管理應用內存
繼承ComponentCallbacks2接口,并onTrimMemory中回調
您的應用可以在處于前臺或后臺時監聽與內存相關的事件,然后釋放對象以響應指示系統需要回收內存的應用生命周期事件或系統事件。
import android.content.ComponentCallbacks2;public class MainActivity extends AppCompatActivityimplements ComponentCallbacks2 {/*** Release memory when the UI becomes hidden or when system resources become low.* @param level the memory-related event that was raised.*/public void onTrimMemory(int level) {// Determine which lifecycle or system event was raised.switch (level) {case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:/*Release any UI objects that currently hold memory.The user interface has moved to the background.*/break;case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:/*Release any memory that your app doesn't need to run.The device is running low on memory while the app is running.The event raised indicates the severity of the memory-related event.If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system willbegin killing background processes.*/break;case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:case ComponentCallbacks2.TRIM_MEMORY_MODERATE:case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:/*Release as much memory as the process can.The app is on the LRU list and the system is running low on memory.The event raised indicates where the app sits within the LRU list.If the event is TRIM_MEMORY_COMPLETE, the process will be one ofthe first to be terminated.*/break;default:/*Release any non-critical data structures.The app received an unrecognized memory level valuefrom the system. Treat this as a generic low-memory message.*/break;}}}查看您應該使用多少內存
private String getAvailableMemory() {ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();activityManager.getMemoryInfo(memoryInfo);StringBuilder sb = new StringBuilder();sb.append("availMem = ").append(memoryInfo.availMem).append("\n").append("totalMem = ").append(memoryInfo.totalMem).append("\n").append("threshold = ").append(memoryInfo.threshold).append("\n").append("lowMemory = ").append(memoryInfo.lowMemory).append("\n");return sb.toString();}怎么引發內存泄漏
Android中的泄漏對象
判斷一個對象是否泄漏首先要判斷該對象是否不再使用,想要判斷這一點則需要對象有明顯的生命周期,在Android中有以下對象可以判斷是否泄漏:
- 1、Activity: 通過Activity的mDestroyed屬性來判斷該Activity是否已經銷毀,如果已經銷毀且未被回收則認為是泄漏
Fragment: 通過Fragment的mFragmentManager是否為空來判斷該Fragment是否處于無用狀態,如果mFragmentManager為空且未被回收則認為是泄漏。 - 2、View: 通過unwrapper mContext獲得Activity,如果存在Activity,則判斷該Activity是否泄漏。
Editor: Editor指的是android.widget包下的Editor,是用于TextView處理editable text的輔助類,通過mTextView是否為空來判斷Editor是否處于無用狀態,如果mTextView為空且未被回收則認為是泄漏。 - 3、ContextWrapper: 通過unwrapper ContextWrapper獲得Activity,如果存在Activity,則判斷該Activity是否泄漏。
- 4、Dialog: 通過mDecor是否為空判斷該Dialog是否處于無用狀態,如果mDecor為空且未被回收則認為是泄漏。
- 5、MessageQueue: 通過mQuitting或者mQuiting(應該是歷史原因,前期拼寫錯誤為mQuiting,后來改正)來判斷MessageQueue是否已經退出,如果已經退出且未被回收則認為是泄漏。
- ViewRootImpl: 通過ViewRootImpl的mView是否為空來判斷該ViewRootImpl是否處于無用狀態,如果mView為空且未被回收則認為是泄漏。
- 6、Window: 通過mDestroyed來判斷該Window是否處于無用狀態,如果mDestroyed為true且未被回收則認為是泄漏。
Toast: 拿到mTN,通過mTN的mView是否為空來判斷當前Toast是否已經hide,如果已經hide且未被回收則認為是泄漏。
泄漏的形式
泄漏的形式
泄漏的本質就是無用對象被持有導致無法回收,具體的形式有如下幾種:
- 1、非靜態內部類、匿名內部類持有外部類對象引用: 一般為用于回調的Listener,該Listener被別的地方持有,間接導致外部類對象被泄漏。
- 2、Handler: 在Activity中定義Handler對象的時候,Handler持有Activity同時Message持有Handler,而Message被MessageQueue持有,最終導致Activity泄漏。
- 3、資源對象未關閉: 數據庫連接、Cursor、IO流等使用完后未close。
- 4、屬性動畫: 使用ValueAnimator和ObjectAnimator的時候,未及時關閉動畫導致泄漏。Animator內部向AnimationHandler注冊listener,AnimationHandler是一個單例,如果不及時cancel,會導致Animator泄漏,間接導致Activity/Fragment/View泄漏(比如Animator的updateListener一般都以匿名內部類實現)
- 5、邏輯問題: 注冊監聽器之后未及時解注冊,比如使用EventBus的時候沒有在合適的時候進行解注冊
Profiler的使用
內存性能分析器Profiler概覽
- 捕獲堆轉儲(Capture heap dump):查看應用程序中在特定時間點使用內存的對象
- 記錄Native分配(Record native allocations):查看每個C/C++對象在一段時間內是如何分配的
- 記錄java/kotlin分配(Record java/kotlin allocations):查看在一段時間內如何分配每個java/kotlin對象
Heap分區
- app heap:當前APP從堆中分配的內存
- image heap:系統啟動映像,包含啟動期間預加載的類。此處的分配保證絕不會移動或消失
- zygote heap:zygote是所有APP進程的母進程,linux的進程使用COW技術,所有的APP共享zygote的內存空間,因此堆的話也繼承了,并且zygote的這些空間不允許寫入,為了加快java的啟動和運行速度,zygote在啟動時預加載了許多資源和代碼,這樣可以提高APP的運行效率。
源碼
Instance View
Depth
depth是從gc roots到選中的當前對象的引用鏈最短長度。在java垃圾回收機制中,被gc roots引用到的對象不會被回收。如下圖:在gc root 引用樹之外的對象會被回收。
Android中gc root主要分為下面幾類:
Native Size
Native Size對象的內存大小,這個值只有在Android 7.0以及更高的版本可見。
Shallow Size
對象本身占用的內存,不包括它引用的其它實例。
Shallow Size = [類定義] + 父類fields所占空間 + 自身fields所占空間 + [alignment]
Retained Size
Retained Size是指,當實例A被回收時,可以同時被回收的實例的Shallow Size之和,所以進行內存分析時,應該重點關注Retained Size較大的實例;或者可以通過Retained Size判斷出某A實例內部使用的實例是否被其它實例引用。
Reference
Reference里面主要是類的引用關系,可以通過引用關系,一層一層的查看類是如何泄漏的
KOOM
KOOM的整體依賴圖
無主動出發GC不卡頓
啟動服務之前創建的Intent傳參
REASON=reanalysis THREAD=714 JAVA_MAX_MEM=192.0 USAGE_TIME=5 DEVICE_AVA_MEM=3476.6563 CURRENT_PAGE=javaleak.JavaLeakTestActivity RESULT_RECEIVER=com.kwai.koom.javaoom.monitor.analysis.AnalysisReceiver@f003f7 FD=32 PSS=62.92871mb RSS=91.58594mb SDK=23 VSS=1643.3594mb TIME=2022-06-19_21-09-10_345 MODEL=MuMu JAVA_USED_MEM=12.838821 HPROF_FILE=/storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oom/memory/hprof-aly/1.0.0_2022-06-19_21-00-03_901.hprof DEVICE_MAX_MEM=3953.0703 MANUFACTURE=Netease JSON_FILE=/storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oom/memory/hprof-aly/1.0.0_2022-06-19_21-00-03_901.json ROOT_PATH=/storage/emulated/0/Android/data/com.kwai.koom.demo/files/performance/oomHeapAnalysisService
/*** Copyright 2021 Kwai, Inc. All rights reserved.* <p>* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at* <p>* http://www.apache.org/licenses/LICENSE-2.0* <p>* Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.* <p>* Heap report file json format.** @author Rui Li <lirui05@kuaishou.com>*/package com.kwai.koom.javaoom.monitor.analysisimport android.annotation.SuppressLint import android.app.IntentService import android.content.Context import android.content.Intent import android.os.Build import android.os.Debug import android.os.ResultReceiver import com.google.gson.Gson import com.kwai.koom.base.MonitorLog import com.kwai.koom.javaoom.monitor.OOMFileManager import com.kwai.koom.javaoom.monitor.OOMFileManager.createDumpFile import com.kwai.koom.javaoom.monitor.OOMFileManager.fdDumpDir import com.kwai.koom.javaoom.monitor.OOMFileManager.threadDumpDir import com.kwai.koom.javaoom.monitor.tracker.model.SystemInfo.javaHeap import com.kwai.koom.javaoom.monitor.tracker.model.SystemInfo.memInfo import com.kwai.koom.javaoom.monitor.tracker.model.SystemInfo.procStatus import com.kwai.koom.javaoom.monitor.utils.SizeUnit.BYTE import com.kwai.koom.javaoom.monitor.utils.SizeUnit.KB import kshark.* import kshark.HeapAnalyzer.FindLeakInput import kshark.HprofHeapGraph.Companion.openHeapGraph import java.io.File import java.io.IOException import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import kotlin.system.measureTimeMillisclass HeapAnalysisService : IntentService("HeapAnalysisService") {companion object {private const val TAG = "OOMMonitor_HeapAnalysisService"private const val OOM_ANALYSIS_TAG = "OOMMonitor"private const val OOM_ANALYSIS_EXCEPTION_TAG = "OOMMonitor_Exception"//Activity->ContextThemeWrapper->ContextWrapper->Context->Objectprivate const val ACTIVITY_CLASS_NAME = "android.app.Activity"//Bitmap->Object//Exception: Some OPPO devicesconst val BITMAP_CLASS_NAME = "android.graphics.Bitmap"//Fragment->Objectprivate const val NATIVE_FRAGMENT_CLASS_NAME = "android.app.Fragment"// native android Fragment, deprecated as of API 28.private const val SUPPORT_FRAGMENT_CLASS_NAME = "android.support.v4.app.Fragment"// pre-androidx, support library version of the Fragment implementation.private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"// androidx version of the Fragment implementation//Window->Objectprivate const val WINDOW_CLASS_NAME = "android.view.Window"//NativeAllocationRegistryprivate const val NATIVE_ALLOCATION_CLASS_NAME = "libcore.util.NativeAllocationRegistry"private const val NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME ="libcore.util.NativeAllocationRegistry\$CleanerThunk"private const val FINISHED_FIELD_NAME = "mFinished"private const val DESTROYED_FIELD_NAME = "mDestroyed"private const val FRAGMENT_MANAGER_FIELD_NAME = "mFragmentManager"private const val FRAGMENT_MCALLED_FIELD_NAME = "mCalled"private const val DEFAULT_BIG_PRIMITIVE_ARRAY = 256 * 1024private const val DEFAULT_BIG_BITMAP = 768 * 1366 + 1private const val DEFAULT_BIG_OBJECT_ARRAY = 256 * 1024private const val SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD = 45 // threshold 閾值annotation class Info {companion object {internal const val HPROF_FILE = "HPROF_FILE"internal const val JSON_FILE = "JSON_FILE"internal const val ROOT_PATH = "ROOT_PATH"internal const val RESULT_RECEIVER = "RESULT_RECEIVER"internal const val JAVA_MAX_MEM = "JAVA_MAX_MEM"internal const val JAVA_USED_MEM = "JAVA_USED_MEM"internal const val DEVICE_MAX_MEM = "DEVICE_MAX_MEM"internal const val DEVICE_AVA_MEM = "DEVICE_AVA_MEM"internal const val VSS = "VSS"internal const val PSS = "PSS"internal const val RSS = "RSS"internal const val FD = "FD"internal const val THREAD = "THREAD"internal const val SDK = "SDK"internal const val MANUFACTURE = "MANUFACTURE"internal const val MODEL = "MODEL"internal const val TIME = "TIME"internal const val REASON = "REASON"internal const val USAGE_TIME = "USAGE_TIME"internal const val CURRENT_PAGE = "CURRENT_PAGE"}}fun startAnalysisService(context: Context, hprofFile: String?, jsonFile: String?,extraData: AnalysisExtraData, resultCallBack: AnalysisReceiver.ResultCallBack?) {MonitorLog.i(TAG, "startAnalysisService")val analysisReceiver = AnalysisReceiver().apply {setResultCallBack(resultCallBack)}val intent = Intent(context, HeapAnalysisService::class.java).apply {putExtra(Info.HPROF_FILE, hprofFile)putExtra(Info.JSON_FILE, jsonFile)putExtra(Info.ROOT_PATH, OOMFileManager.rootDir.absolutePath)putExtra(Info.RESULT_RECEIVER, analysisReceiver)putExtra(Info.JAVA_MAX_MEM, BYTE.toMB(javaHeap.max).toString())putExtra(Info.JAVA_USED_MEM, BYTE.toMB(javaHeap.total - javaHeap.free).toString())putExtra(Info.DEVICE_MAX_MEM, KB.toMB(memInfo.totalInKb).toString())putExtra(Info.DEVICE_AVA_MEM, KB.toMB(memInfo.availableInKb).toString())putExtra(Info.FD, (File("/proc/self/fd").listFiles()?.size ?: 0).toString())val pss = Debug.getPss() // 實際使用的物理內存MonitorLog.i(TAG, "startAnalysisService get Pss:${pss}")putExtra(Info.PSS, KB.toMB(pss).toString() + "mb")putExtra(Info.VSS, KB.toMB(procStatus.vssInKb).toString() + "mb") // 虛擬耗用內存(包含共享庫占用的全部內存,以及分配但未使用內存)putExtra(Info.RSS, KB.toMB(procStatus.rssInKb).toString() + "mb") // 即使用的物理內存putExtra(Info.THREAD, procStatus.thread.toString()) //putExtra(Info.MANUFACTURE, Build.MANUFACTURER.toString())putExtra(Info.SDK, Build.VERSION.SDK_INT.toString())putExtra(Info.MODEL, Build.MODEL.toString())putExtra(Info.TIME, SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS", Locale.CHINESE).format(Date()))if (extraData.reason != null) {putExtra(Info.REASON, extraData.reason)}if (extraData.currentPage != null) {putExtra(Info.CURRENT_PAGE, extraData.currentPage)}if (extraData.usageSeconds != null) {putExtra(Info.USAGE_TIME, extraData.usageSeconds)}}val sb = StringBuilder();for (key in intent.extras?.keySet().orEmpty()) {sb.append(key).append("=").append(intent.extras?.get(key)).appendln();}MonitorLog.i(TAG, sb.toString())context.startService(intent)}}private lateinit var mHeapGraph: HeapGraphprivate val mLeakModel = HeapReport()private val mLeakingObjectIds = mutableSetOf<Long>()private val mLeakReasonTable = mutableMapOf<Long, String>()override fun onHandleIntent(intent: Intent?) {MonitorLog.i(TAG, "onHandleIntent")val resultReceiver = intent?.getParcelableExtra<ResultReceiver>(Info.RESULT_RECEIVER)val hprofFile = intent?.getStringExtra(Info.HPROF_FILE)val jsonFile = intent?.getStringExtra(Info.JSON_FILE)val rootPath = intent?.getStringExtra(Info.ROOT_PATH)OOMFileManager.init(rootPath)kotlin.runCatching {// 解析堆轉儲文件buildIndex(hprofFile)}.onFailure {it.printStackTrace()MonitorLog.e(OOM_ANALYSIS_EXCEPTION_TAG, "build index exception " + it.message, true)resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)return}// 只是創建一個文件buildJson(intent)kotlin.runCatching {// 篩選出內存泄漏的對象filterLeakingObjects()}.onFailure {MonitorLog.i(OOM_ANALYSIS_EXCEPTION_TAG, "find leak objects exception " + it.message, true)resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)return}kotlin.runCatching {// 從GcRoot到泄漏點的路徑findPathsToGcRoot()}.onFailure {it.printStackTrace()MonitorLog.i(OOM_ANALYSIS_EXCEPTION_TAG, "find gc path exception " + it.message, true)resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)return}fillJsonFile(jsonFile)resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK, null)//exitProcess(0)}@SuppressLint("SdCardPath")private fun buildIndex(hprofFile: String?) {if (hprofFile.isNullOrEmpty()) returnMonitorLog.i(TAG, "start analyze")SharkLog.logger = object : SharkLog.Logger {override fun d(message: String) {println(message)}override fun d(throwable: Throwable,message: String) {println(message)throwable.printStackTrace()}}measureTimeMillis {mHeapGraph = File(hprofFile).openHeapGraph(null,setOf(HprofRecordTag.ROOT_JNI_GLOBAL,HprofRecordTag.ROOT_JNI_LOCAL,HprofRecordTag.ROOT_NATIVE_STACK,HprofRecordTag.ROOT_STICKY_CLASS,HprofRecordTag.ROOT_THREAD_BLOCK,HprofRecordTag.ROOT_THREAD_OBJECT))}.also {try {MonitorLog.i(TAG, "build index cost time: 開始") // val toJson = Gson().toJson(mHeapGraph) // val toJson = mHeapGraph.toString()MonitorLog.i(TAG, "build index cost time: 生成JSON") // val file = File("/sdcard/Android/data/com.kwai.koom.demo/cache/heapgraph.txt") // MonitorLog.i(TAG, "build index cost time: 寫入本地") // file.writeText(toJson)MonitorLog.i(TAG, "build index cost time: 本地本地完成")} catch (e: Exception) {e.printStackTrace()val file = File("/sdcard/Android/data/com.kwai.koom.demo/cache/heapgraph.txt")file.writeText(e.message ?: "error")}MonitorLog.i(TAG, "build index cost time: $it")}}private fun buildJson(intent: Intent?) {mLeakModel.runningInfo = HeapReport.RunningInfo().apply {jvmMax = intent?.getStringExtra(Info.JAVA_MAX_MEM)jvmUsed = intent?.getStringExtra(Info.JAVA_USED_MEM)threadCount = intent?.getStringExtra(Info.THREAD)fdCount = intent?.getStringExtra(Info.FD)vss = intent?.getStringExtra(Info.VSS)pss = intent?.getStringExtra(Info.PSS)rss = intent?.getStringExtra(Info.RSS)sdkInt = intent?.getStringExtra(Info.SDK)manufacture = intent?.getStringExtra(Info.MANUFACTURE)buildModel = intent?.getStringExtra(Info.MODEL)usageSeconds = intent?.getStringExtra(Info.USAGE_TIME)currentPage = intent?.getStringExtra(Info.CURRENT_PAGE)nowTime = intent?.getStringExtra(Info.TIME)deviceMemTotal = intent?.getStringExtra(Info.DEVICE_MAX_MEM)deviceMemAvaliable = intent?.getStringExtra(Info.DEVICE_AVA_MEM)dumpReason = intent?.getStringExtra(Info.REASON)MonitorLog.i(TAG, "handle Intent, fdCount:${fdCount} pss:${pss} rss:${rss} vss:${vss} " +"threadCount:${threadCount}")fdList = createDumpFile(fdDumpDir).takeIf { it.exists() }?.readLines()threadList = createDumpFile(threadDumpDir).takeIf { it.exists() }?.readLines()// createDumpFile(fdDumpDir).delete() // createDumpFile(threadDumpDir).delete()}}/*** 遍歷鏡像所有class查找** 計算gc path:* 1.已經destroyed和finished的activity* 2.已經fragment manager為空的fragment* 3.已經destroyed的window* 4.超過閾值大小的bitmap* 5.超過閾值大小的基本類型數組* 6.超過閾值大小的對象個數的任意class*** 記錄關鍵類:* 對象數量* 1.基本類型數組* 2.Bitmap* 3.NativeAllocationRegistry* 4.超過閾值大小的對象的任意class*** 記錄大對象:* 對象大小* 1.Bitmap* 2.基本類型數組*/private fun filterLeakingObjects() {val startTime = System.currentTimeMillis()MonitorLog.i(TAG, "filterLeakingObjects " + Thread.currentThread())// 各種內存泄漏的點val activityHeapClass: HeapObject.HeapClass? = mHeapGraph.findClassByName(ACTIVITY_CLASS_NAME)MonitorLog.i(TAG, "filterLeakingObjects activityHeapClass = $activityHeapClass")val fragmentHeapClass: HeapObject.HeapClass? = mHeapGraph.findClassByName(ANDROIDX_FRAGMENT_CLASS_NAME)?: mHeapGraph.findClassByName(NATIVE_FRAGMENT_CLASS_NAME)?: mHeapGraph.findClassByName(SUPPORT_FRAGMENT_CLASS_NAME)MonitorLog.i(TAG, "filterLeakingObjects fragmentHeapClass = $fragmentHeapClass")val bitmapHeapClass: HeapObject.HeapClass? = mHeapGraph.findClassByName(BITMAP_CLASS_NAME)MonitorLog.i(TAG, "filterLeakingObjects bitmapHeapClass = $bitmapHeapClass")val nativeAllocationHeapClass: HeapObject.HeapClass? = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLASS_NAME)MonitorLog.i(TAG, "filterLeakingObjects nativeAllocationHeapClass = $nativeAllocationHeapClass")val nativeAllocationThunkHeapClass: HeapObject.HeapClass? = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME)MonitorLog.i(TAG, "filterLeakingObjects nativeAllocationThunkHeapClass = $nativeAllocationThunkHeapClass")val windowClass: HeapObject.HeapClass? = mHeapGraph.findClassByName(WINDOW_CLASS_NAME)MonitorLog.i(TAG, "filterLeakingObjects windowClass = $windowClass")//緩存classHierarchy,用于查找class的所有instanceval classHierarchyMap = mutableMapOf<Long, Pair<Long, Long>>()//記錄class objects數量val classObjectCounterMap = mutableMapOf<Long, ObjectCounter>()//遍歷鏡像的所有instancefor (instance: HeapObject.HeapInstance in mHeapGraph.instances) {//HprofHeapGraph.log("HeapObject.HeapInstance -> ${instance.instanceClassName}")if (instance.isPrimitiveWrapper) { // Byte,Short,Integer,Long,Float.Double,Character,Booleancontinue}//使用HashMap緩存及遍歷兩邊classHierarchy,這2種方式加速查找instance是否是對應類實例//superId1代表類的繼承層次中倒數第一的id,0就是繼承自object//superId4代表類的繼承層次中倒數第四的id//類的繼承關系,以AOSP代碼為主,部分廠商入如OPPO Bitmap會做一些修改,這里先忽略val instanceClassId: Long = instance.instanceClassId//MonitorLog.i(TAG, "instance instanceClassId = $instanceClassId")// instance instanceClassId = 1896487800val (superId1: Long, superId4: Long) = if (classHierarchyMap[instanceClassId] != null) {classHierarchyMap[instanceClassId]!! // mutableMapOf<Long, Pair<Long, Long>>()} else {val classHierarchyList = instance.instanceClass.classHierarchy.toList()val first = classHierarchyList.getOrNull(classHierarchyList.size - 2)?.objectId ?: 0Lval second = classHierarchyList.getOrNull(classHierarchyList.size - 5)?.objectId ?: 0LPair(first, second).also { classHierarchyMap[instanceClassId] = it }}//MonitorLog.i(TAG, "instance superId1 = $superId1 , superId4 = $superId4")// instance superId1 = 1895815168 , superId4 = 0// 如何判斷是泄露的那個類的//Activityif (activityHeapClass?.objectId == superId4) {val destroyField: HeapField = instance[ACTIVITY_CLASS_NAME, DESTROYED_FIELD_NAME]!!val finishedField: HeapField = instance[ACTIVITY_CLASS_NAME, FINISHED_FIELD_NAME]!!if (destroyField.value.asBoolean!! || finishedField.value.asBoolean!!) {val objectCounter = updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, true)MonitorLog.i(TAG, "activity name : " + instance.instanceClassName+ " mDestroyed:" + destroyField.value.asBoolean+ " mFinished:" + finishedField.value.asBoolean+ " objectId:" + (instance.objectId and 0xffffffffL))if (objectCounter.leakCnt <= SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD) {mLeakingObjectIds.add(instance.objectId)mLeakReasonTable[instance.objectId] = "Activity Leak"MonitorLog.i(OOM_ANALYSIS_TAG, "${instance.instanceClassName} objectId:${instance.objectId}")}} else {MonitorLog.i(TAG, "activity name : " + instance.instanceClassName+ " mDestroyed:" + destroyField.value.asBoolean+ " mFinished:" + finishedField.value.asBoolean+ " objectId:" + (instance.objectId and 0xffffffffL))}continue // 找到了就不往下了}//Fragmentif (fragmentHeapClass?.objectId == superId1) {val fragmentManager: HeapField? = instance[fragmentHeapClass.name, FRAGMENT_MANAGER_FIELD_NAME]if (fragmentManager != null && fragmentManager.value.asObject == null) {val mCalledField: HeapField? = instance[fragmentHeapClass.name, FRAGMENT_MCALLED_FIELD_NAME]//mCalled為true且fragment manager為空時認為fragment已經destroyval isLeak: Boolean = mCalledField != null && mCalledField.value.asBoolean!!val objectCounter: ObjectCounter =updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, isLeak)MonitorLog.i(TAG, "fragment name:" + instance.instanceClassName + " isLeak:" + isLeak)if (objectCounter.leakCnt <= SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD && isLeak) {mLeakingObjectIds.add(instance.objectId)mLeakReasonTable[instance.objectId] = "Fragment Leak"MonitorLog.i(OOM_ANALYSIS_TAG,instance.instanceClassName + " objectId:" + instance.objectId)}}continue}//Bitmapif (bitmapHeapClass?.objectId == superId1) {val fieldWidth = instance[BITMAP_CLASS_NAME, "mWidth"]val fieldHeight = instance[BITMAP_CLASS_NAME, "mHeight"]val width = fieldWidth!!.value.asInt!!val height = fieldHeight!!.value.asInt!!if (width * height >= DEFAULT_BIG_BITMAP) {val objectCounter = updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, true)MonitorLog.e(TAG, "suspect leak! bitmap name: ${instance.instanceClassName}" +" width: ${width} height:${height}")if (objectCounter.leakCnt <= SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD) {mLeakingObjectIds.add(instance.objectId)mLeakReasonTable[instance.objectId] = "Bitmap Size Over Threshold, ${width}x${height}"MonitorLog.i(OOM_ANALYSIS_TAG,instance.instanceClassName + " objectId:" + instance.objectId)//加入大對象泄露jsonval leakObject: HeapReport.LeakObject = HeapReport.LeakObject().apply {className = instance.instanceClassNamesize = (width * height).toString()extDetail = "$width x $height"objectId = (instance.objectId and 0xffffffffL).toString()}mLeakModel.leakObjects.add(leakObject)}}continue}//nativeallocation/NativeAllocationThunk/windowif (nativeAllocationHeapClass?.objectId == superId1|| nativeAllocationThunkHeapClass?.objectId == superId1|| windowClass?.objectId == superId1) {updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, false)}}//關注class和對應instance數量,加入jsonfor ((instanceId, objectCounter) in classObjectCounterMap) {val leakClass: HeapReport.ClassInfo = HeapReport.ClassInfo().apply {val heapClass: HeapObject.HeapClass? = mHeapGraph.findObjectById(instanceId).asClassclassName = heapClass?.nameinstanceCount = objectCounter.allCnt.toString()MonitorLog.i(OOM_ANALYSIS_TAG, "leakClass.className: $className leakClass.objectCount: $instanceCount")}mLeakModel.classInfos.add(leakClass)}//查找基本類型數組val primitiveArrayIterator: Iterator<HeapObject.HeapPrimitiveArray> = mHeapGraph.primitiveArrays.iterator()while (primitiveArrayIterator.hasNext()) {val primitiveArray = primitiveArrayIterator.next()val arraySize = primitiveArray.recordSizeif (arraySize >= DEFAULT_BIG_PRIMITIVE_ARRAY) {val arrayName = primitiveArray.arrayClassNameval typeName = primitiveArray.primitiveType.toString()MonitorLog.e(OOM_ANALYSIS_TAG,"uspect leak! primitive arrayName:" + arrayName+ " size:" + arraySize + " typeName:" + typeName+ ", objectId:" + (primitiveArray.objectId and 0xffffffffL)+ ", toString:" + primitiveArray.toString())mLeakingObjectIds.add(primitiveArray.objectId)mLeakReasonTable[primitiveArray.objectId] = "Primitive Array Size Over Threshold, ${arraySize}"val leakObject = HeapReport.LeakObject().apply {className = arrayNamesize = arraySize.toString()objectId = (primitiveArray.objectId and 0xffffffffL).toString()}mLeakModel.leakObjects.add(leakObject)}}//查找對象數組val objectArrayIterator = mHeapGraph.objectArrays.iterator()while (objectArrayIterator.hasNext()) {val objectArray = objectArrayIterator.next()val arraySize = objectArray.recordSizeif (arraySize >= DEFAULT_BIG_OBJECT_ARRAY) {val arrayName = objectArray.arrayClassNameMonitorLog.i(OOM_ANALYSIS_TAG,"object arrayName:" + arrayName + " objectId:" + objectArray.objectId)mLeakingObjectIds.add(objectArray.objectId)val leakObject = HeapReport.LeakObject().apply {className = arrayNamesize = arraySize.toString()objectId = (objectArray.objectId and 0xffffffffL).toString()}mLeakModel.leakObjects.add(leakObject)}}val endTime = System.currentTimeMillis()mLeakModel.runningInfo?.filterInstanceTime = ((endTime - startTime).toFloat() / 1000).toString()MonitorLog.i(OOM_ANALYSIS_TAG, "filterLeakingObjects time:" + 1.0f * (endTime - startTime) / 1000)}// GCRootPath是什么private fun findPathsToGcRoot() {val startTime = System.currentTimeMillis()val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener { step: OnAnalysisProgressListener.Step ->MonitorLog.i(TAG, "step:" + step.name + ", leaking obj size:" + mLeakingObjectIds.size)})val findLeakInput: FindLeakInput =FindLeakInput(mHeapGraph, AndroidReferenceMatchers.appDefaults, false, mutableListOf())val (applicationLeaks: List<ApplicationLeak>, libraryLeaks: List<LibraryLeak>) = with(heapAnalyzer) {MonitorLog.i(OOM_ANALYSIS_TAG, "mLeakingObjectIds = $mLeakingObjectIds")findLeakInput.findLeaks(mLeakingObjectIds)}MonitorLog.i(OOM_ANALYSIS_TAG,"---------------------------Application Leak---------------------------------------")//填充application leakMonitorLog.i(OOM_ANALYSIS_TAG, "ApplicationLeak size:" + applicationLeaks.size)for (applicationLeak in applicationLeaks) {MonitorLog.i(OOM_ANALYSIS_TAG, "shortDescription:" + applicationLeak.shortDescription+ ", signature:" + applicationLeak.signature+ " same leak size:" + applicationLeak.leakTraces.size)val (gcRootType, referencePath, leakTraceObject) = applicationLeak.leakTraces[0]val gcRoot: String = gcRootType.descriptionval labels: Array<String> = leakTraceObject.labels.toTypedArray()leakTraceObject.leakingStatusReason = mLeakReasonTable[leakTraceObject.objectId].toString()MonitorLog.i(OOM_ANALYSIS_TAG, "GC Root:" + gcRoot+ ", leakObjClazz:" + leakTraceObject.className+ ", leakObjType:" + leakTraceObject.typeName+ ", labels:" + labels.contentToString()+ ", leaking reason:" + leakTraceObject.leakingStatusReason+ ", leaking obj:" + (leakTraceObject.objectId and 0xffffffffL))val leakTraceChainModel = HeapReport.GCPath().apply {this.instanceCount = applicationLeak.leakTraces.sizethis.leakReason = leakTraceObject.leakingStatusReasonthis.gcRoot = gcRootthis.signature = applicationLeak.signature}.also { mLeakModel.gcPaths.add(it) }// 添加索引到的trace pathfor (reference in referencePath) {val referenceName: String = reference.referenceNameval clazz: String = reference.originObject.classNameval referenceDisplayName: String = reference.referenceDisplayNameval referenceGenericName: String = reference.referenceGenericNameval referenceType: String = reference.referenceType.toString()val declaredClassName: String = reference.owningClassNameMonitorLog.i(OOM_ANALYSIS_TAG, "clazz:" + clazz +", referenceName:" + referenceName+ ", referenceDisplayName:" + referenceDisplayName+ ", referenceGenericName:" + referenceGenericName+ ", referenceType:" + referenceType+ ", declaredClassName:" + declaredClassName)val leakPathItem: HeapReport.GCPath.PathItem = HeapReport.GCPath.PathItem().apply {this.reference = if (referenceDisplayName.startsWith("[")) //數組類型[]clazzelse"$clazz.$referenceDisplayName"this.referenceType = referenceTypethis.declaredClass = declaredClassName}leakTraceChainModel.path.add(leakPathItem)}// 添加本身trace pathleakTraceChainModel.path.add(HeapReport.GCPath.PathItem().apply {reference = leakTraceObject.classNamereferenceType = leakTraceObject.typeName})}MonitorLog.i(OOM_ANALYSIS_TAG, "=======================================================================")MonitorLog.i(OOM_ANALYSIS_TAG,"----------------------------Library Leak--------------------------------------")//填充library leakMonitorLog.i(OOM_ANALYSIS_TAG, "LibraryLeak size:" + libraryLeaks.size)for (libraryLeak in libraryLeaks) {MonitorLog.i(OOM_ANALYSIS_TAG, "description:" + libraryLeak.description+ ", shortDescription:" + libraryLeak.shortDescription+ ", pattern:" + libraryLeak.pattern.toString())val (gcRootType, referencePath, leakTraceObject) = libraryLeak.leakTraces[0]val gcRoot: String = gcRootType.descriptionval labels: Array<String> = leakTraceObject.labels.toTypedArray()leakTraceObject.leakingStatusReason = mLeakReasonTable[leakTraceObject.objectId].toString()MonitorLog.i(OOM_ANALYSIS_TAG, "GC Root:" + gcRoot+ ", leakClazz:" + leakTraceObject.className+ ", labels:" + labels.contentToString()+ ", leaking reason:" + leakTraceObject.leakingStatusReason)val leakTraceChainModel: HeapReport.GCPath = HeapReport.GCPath().apply {this.instanceCount = libraryLeak.leakTraces.sizethis.leakReason = leakTraceObject.leakingStatusReasonthis.signature = libraryLeak.signaturethis.gcRoot = gcRoot}mLeakModel.gcPaths.add(leakTraceChainModel)// 添加索引到的trace pathfor (reference in referencePath) {val clazz: String = reference.originObject.classNameval referenceName: String = reference.referenceNameval referenceDisplayName: String = reference.referenceDisplayNameval referenceGenericName: String = reference.referenceGenericNameval referenceType: String = reference.referenceType.toString()val declaredClassName: String = reference.owningClassNameMonitorLog.i(OOM_ANALYSIS_TAG, "clazz:" + clazz +", referenceName:" + referenceName+ ", referenceDisplayName:" + referenceDisplayName+ ", referenceGenericName:" + referenceGenericName+ ", referenceType:" + referenceType+ ", declaredClassName:" + declaredClassName)val leakPathItem: HeapReport.GCPath.PathItem = HeapReport.GCPath.PathItem().apply {this.reference = if (referenceDisplayName.startsWith("["))clazzelse //數組類型[]"$clazz.$referenceDisplayName"this.referenceType = referenceTypethis.declaredClass = declaredClassName}leakTraceChainModel.path.add(leakPathItem)}// 添加本身trace pathleakTraceChainModel.path.add(HeapReport.GCPath.PathItem().apply {reference = leakTraceObject.classNamereferenceType = leakTraceObject.typeName})break}MonitorLog.i(OOM_ANALYSIS_TAG,"=======================================================================")val endTime = System.currentTimeMillis()mLeakModel.runningInfo!!.findGCPathTime = ((endTime - startTime).toFloat() / 1000).toString()MonitorLog.i(OOM_ANALYSIS_TAG, "findPathsToGcRoot cost time: "+ (endTime - startTime).toFloat() / 1000)}private fun fillJsonFile(jsonFile: String?) {val json = Gson().toJson(mLeakModel)try {jsonFile?.let { File(it) }?.writeText(json)MonitorLog.i(OOM_ANALYSIS_TAG, "JSON write success: $json")} catch (e: IOException) {e.printStackTrace()MonitorLog.i(OOM_ANALYSIS_TAG, "JSON write exception: $json", true)}}private fun updateClassObjectCounterMap(classObCountMap: MutableMap<Long, ObjectCounter>,instanceClassId: Long,isLeak: Boolean): ObjectCounter {val objectCounter = classObCountMap[instanceClassId] ?: ObjectCounter().also {classObCountMap[instanceClassId] = it}objectCounter.allCnt++if (isLeak) {objectCounter.leakCnt++}return objectCounter}class ObjectCounter {var allCnt = 0 // 需要自己+1var leakCnt = 0 // 需要自己+1} }HeapGraph
ForkJvmHeapDumper
/** Copyright 2020 Kwai, Inc. All rights reserved.* <p>* Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at* <p>* http://www.apache.org/licenses/LICENSE-2.0* <p>* Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.* <p>* A jvm hprof dumper which use fork and don't block main process.** @author Rui Li <lirui05@kuaishou.com>*/package com.kwai.koom.javaoom.hprof;import java.io.IOException;import android.os.Build; import android.os.Debug;import com.kwai.koom.base.MonitorLog;public class ForkJvmHeapDumper extends HeapDumper {private static final String TAG = "OOMMonitor_ForkJvmHeapDumper";public ForkJvmHeapDumper() {super();if (soLoaded) {init(); // 打開系統的庫函數}}@Overridepublic boolean dump(String path) {MonitorLog.i(TAG, "dump " + path);if (!soLoaded) {MonitorLog.e(TAG, "dump failed caused by so not loaded!");return false;}if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP|| Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {MonitorLog.e(TAG, "dump failed caused by version not supported!");return false;}boolean dumpRes = false;try {MonitorLog.i(TAG, "before suspend and fork.");// 創建子進程// pid返回值// 1。 返回子進程id給父進程:// 因為一個進程的子進程可能有多個,并且沒有一個函數可以獲得一個進程的所有子進程// 2。 返回給子進程值為0:// 一個進程只會有一個,所以子進程松獅可以調用getid以獲得當前進程id以調用getid獲得父進程id// 3。 出現錯誤,返回負值:// 當前進程數已經達到系統規定的上限,這是errno的值被設置為EAGAIN// 系統內存不足,這時errno的值被設置為ENOMEMint pid = suspendAndFork();if (pid == 0) {// Child processDebug.dumpHprofData(path);exitProcess();} else if (pid > 0) {// Parent processdumpRes = resumeAndWait(pid);MonitorLog.i(TAG, "notify from pid " + pid);}} catch (IOException e) {MonitorLog.e(TAG, "dump failed caused by " + e.toString());e.printStackTrace();}return dumpRes;}// ---->https://www.jianshu.com/p/586300fdb1ce<----// int main(int argc, char *argv[]) {// pid_t pid;// int count = 0;// //獲得當前進程ID// printf("Current Process Id = %d\n", getpid());// if ((pid == fork()) < 0) {// print("異常退出");// } else if(pid == 0) {// count++;// printf("進程子進程,當前進程curretPid=%d, 父進程parentPid=%d\n", getpid(), getppid())// } else {// count++;// printf("當前進程 currentPid = %d, Count = %d\n", getpid(), count);// }// printf("當前進程 currentPid = %d, Count = %d\n", getpid(), count);// return 0// }/*** Init before do dump.* // 打開系統的庫函數*/private native void init();/*** Suspend the whole ART, and then fork a process for dumping hprof.** @return return value of fork*/private native int suspendAndFork();/*** Resume the whole ART, and then wait child process to notify.** @param pid pid of child process.*/private native boolean resumeAndWait(int pid);/*** Exit current process.*/private native void exitProcess(); }JavaHeap(內存使用信息)
JavaHeap(max=402653184, total=27552536, free=20674832, used=6877704, rate=0.017080963) data class JavaHeap(var max: Long = 0, // Runtime.getRuntim().maxMemory()var total: Long = 0, // Runtime.getRuntime().totalMemory()var free: Long = 0, // Runtime.getRuntime().freeMemory()var used: Long = 0, // javaHeap.total - javaHeap.freevar rate: Float = 0f // 1.0f * javaHeap.used / javaHeap.max)ProcStatus
ProcStatus(thread=731, vssInKb=18421872, rssInKb=138368) data class ProcStatus(var thread: Int = 0, var vssInKb: Int = 0, var rssInKb: Int = 0) flannery@zhaojiandeMacBook-Pro-2 KOOM % adb shell HWANA:/ $ cat /proc/self/status Name: cat // 應用程序或命令的名字 Umask: 0000 State: R (running) // 任務的狀態,運行/睡眠/僵死 Tgid: 30578 // 線程組號 Ngid: 0 Pid: 30578 // 任務ID進程ID PPid: 30572 // 父進程ID TracerPid: 0 // 接受跟蹤該進程信息的ID號 Uid: 2000 2000 2000 2000 (Uid euid suid fsuid) Gid: 2000 2000 2000 2000 (Gid eguid sgid fsgid) FDSize: 64 // 文件描述符的最大個數,file->fds Groups: 1004 1007 1011 1015 1028 3001 3002 3003 3006 3009 3011 VmPeak: 31132 kB VmSize: 30232 kB // 任務虛擬地址空間的大小 (total_vm-reserved_vm),其中total_vm為進程的地址空間的大小,reserved_vm:進程在預留或特殊的內存間的物理頁 VmLck: 0 kB // 任務已經鎖住的物理內存的大小。鎖住的物理內存不能交換到硬盤(locked_vm) VmPin: 0 kB VmHWM: 3160 kB VmRSS: 3140 kB // 應用程序正在使用的物理內存的大小,就是用ps命令的參數rss的值(rss)。 RssAnon: 600 kB RssFile: 2360 kB RssShmem: 180 kB VmData: 4904 kB // 程序數據段的大小(所占虛擬內存的大小),存放初始化了的數據;(total_vm-shared_vm-stack_vm) VmStk: 136 kB // 任務在用戶態的棧的大小(stack_vm) VmExe: 432 kB // 程序所擁有的可執行虛擬內存的大小,代碼段,不包括任務使用的庫(end_code-start_code) VmLib: 3156 kB // 被映像到任務的虛擬內存空間的庫的大小(exec_lib) VmPTE: 48 kB // 該進程的所有頁表的大小,單位kb。Threads共享使用該信號描述符任務的個數,在POSIX多線程應用程序中,線程組中的所有線程使用同一個信號描述符。 VmPMD: 12 kB // VmSwap: 0 kB Threads: 1 // SigQ: 0/25471 // 待處理信號的個數 SigPnd: 0000000000000000 // 屏蔽位,存儲了該線程的待處理信號 ShdPnd: 0000000000000000 // 屏蔽為,存儲了該線程組的待處理信號 SigBlk: 0000000080000000 // 存放被阻塞的信號 SigIgn: 0000000000000000 // 存放被忽略的信號 SigCgt: 0000000c400084f8 // 存放被俘獲到的信號 CapInh: 0000000000000000 // CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 00000000000000c0 CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Speculation_Store_Bypass: thread vulnerable Cpus_allowed: ff Cpus_allowed_list: 0-7 Mems_allowed: 1 Mems_allowed_list: 0 voluntary_ctxt_switches: 0 nonvoluntary_ctxt_switches: 1MemInfo
data class MemInfo(var totalInKb: Int = 0,var freeInKb: Int = 0,var availableInKb: Int = 0,var IONHeap: Int = 0,var cmaTotal: Int = 0,var rate: Float = 0f) PD1728:/ $ cat /proc/meminfo MemTotal: 5839880 kB // 可供kernel支配的內存 MemFree: 436760 kB // 表示系統尚未使用的內存 MemAvailable: 2830796 kB // 內核使用特定的算法估算出來的,不精確 Buffers: 145848 kB // Cached: 2336296 kB SwapCached: 10448 kB Active: 1849328 kB Inactive: 1849508 kB Active(anon): 886964 kB Inactive(anon): 333128 kB Active(file): 962364 kB Inactive(file): 1516380 kB Unevictable: 3592 kB Mlocked: 3592 kB SwapTotal: 2097148 kB SwapFree: 587260 kB Dirty: 96 kB Writeback: 0 kB AnonPages: 1216292 kB Mapped: 882444 kB Shmem: 780 kB Slab: 365740 kB SReclaimable: 133356 kB SUnreclaim: 232384 kB KernelStack: 85952 kB PageTables: 112180 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 5017088 kB Committed_AS: 153060520 kB VmallocTotal: 258867136 kB VmallocUsed: 0 kB // VmallocChunk: 0 kB CmaTotal: 176128 kB CmaFree: 8076 kB PD1728:/ $hprofStringCache
hprofStringCache first->4224708 , second->accessibility_custom_action_22 hprofStringCache first->4203524 , second->taskAffinity hprofStringCache first->4208309 , second->Theme_textAppearanceEasyCorrectSuggestion hprofStringCache first->4199920 , second->AndroidManifestActivity_enabled hprofStringCache first->4205019 , second->TRANSACTION_partitionMixed hprofStringCache first->4220338 , second->mOnPreparedListener hprofStringCache first->4227170 , second->androidx.appcompat.view.ViewPropertyAnimatorCompatSet hprofStringCache first->4219142 , second->m_version_ hprofStringCache first->4219707 , second->secprops hprofStringCache first->4194621 , second->dalvik.system.CloseGuard$DefaultReporter hprofStringCache first->4197531 , second->endTime hprofStringCache first->4204476 , second->LINECAP_ROUND hprofStringCache first->4199289 , second->id_aa_ets_contentTimestamp hprofStringCache first->4219711 , second->ABASE hprofStringCache first->4219795 , second->tomorrow hprofStringCache first->4212952 , second->ACQUIRE_CAUSES_WAKEUP hprofStringCache first->4214386 , second->defaultUncaughtHandler hprofStringCache first->4210042 , second->mCurSurfaceHolder hprofStringCache first->4195686 , second->roundingIncrement hprofStringCache first->4217322 , second->mFpsNumFrames hprofStringCache first->4202630 , second->android.app.Fragment hprofStringCache first->4217865 , second->EXTRA_CONNECTION_STATE hprofStringCache first->4217949 , second->CHUNK_THST hprofStringCache first->4207631 , second->INDEX_CENTER_VERTICAL hprofStringCache first->4207766 , second->CENTER_INSIDELeakCanary
LeakCanary#install
public static RefWatcher install(Application application,String processType, String remotePath,Class<? extends AbstractAnalysisResultService> listenerServiceClass,ExcludedRefs excludedRefs) {// 判斷install是否在Analyzer進程里,重復執行if (isInAnalyzerProcess(application)) {return RefWatcher.DISABLED;}enableDisplayLeakActivity(application);// 創建監聽器HeapDump.Listener heapDumpListener =new ServiceHeapDumpListener(application, processType, listenerServiceClass);// Refwatcher 監控引用RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);// ActivityRefWatcher 傳入 application 和 refWatcherActivityRefWatcher.installOnIcsPlus(application, refWatcher);return refWatcher;}ActivityRefWatcher
public final class ActivityRefWatcher {@Deprecatedpublic static void installOnIcsPlus(Application application, RefWatcher refWatcher) {install(application, refWatcher);}public static void install(Application application, RefWatcher refWatcher) {new ActivityRefWatcher(application, refWatcher).watchActivities();}private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =new Application.ActivityLifecycleCallbacks() {@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}@Override public void onActivityStarted(Activity activity) {}@Override public void onActivityResumed(Activity activity) {}@Override public void onActivityPaused(Activity activity) {}@Override public void onActivityStopped(Activity activity) {}@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}// 將Activity中的onActivityDestoryed事件注冊上去@Override public void onActivityDestroyed(Activity activity) {ActivityRefWatcher.this.onActivityDestroyed(activity);}};// 將 activity 事件傳遞到了 refwatchervoid onActivityDestroyed(Activity activity) {// 回調了 refWathcher中的watch方法監控activityrefWatcher.watch(activity);} }RefWatcher
public final class RefWatcher {// 執行內存泄漏檢測的 executorprivate final Executor watchExecutor; // 用于查詢是否正在調試中,調試中不會執行內存泄漏檢測private final DebuggerControl debuggerControl; // 用于在判斷內存泄露之前,再給一次GC的機會 private final GcTrigger gcTrigger; // dump 處內存泄漏的 heapprivate final HeapDumper heapDumper; // 持有那些待檢測以及產生內存泄露的引用的keyprivate final Set<String> retainedKeys; // 用于判斷弱引用所持有的對象是否已被GCprivate final ReferenceQueue<Object> queue; // 用于分析產生的heap文件private final HeapDump.Listener heapdumpListener; // 排除一些系統的bug引起的內存泄漏private final ExcludedRefs excludedRefs; public RefWatcher(Executor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {}//...public void watch(Object watchedReference, String referenceName) {final long watchStartNanoTime = System.nanoTime();// 對一個 referenc 添加唯一的一個 keyString key = UUID.randomUUID().toString();retainedKeys.add(key);// 將 watch 傳入的對象添加一個弱引用final KeyedWeakReference reference =new KeyedWeakReference(watchedReference, key, referenceName, queue);// 在異步線程上開始分析這個弱引用watchExecutor.execute(new Runnable() {@Override public void run() {ensureGone(reference, watchStartNanoTime);}});}RefWatcher#ensureGone
// 避免因為gc不及時帶來的誤判,leakcanay會進行二次確認進行保證void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {long gcStartNanoTime = System.nanoTime();// 計算機從調用 watch 到 gc 所用時間long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);// 清除此時已經到 RQ 的弱引用// 首先調用 removeWeaklyReachableReferences// 把已被回收的對象的 key 從 retainedKeys 移除,剩下的 key 都是未被回收的對象removeWeaklyReachableReferences();// 如果當前的對象已經弱可達,說明不會造成內存泄漏if (gone(reference) || debuggerControl.isDebuggerAttached()) {return;}// 如果當前檢測對象沒有改變其可達狀態,則進行手動GCgcTrigger.runGc();// 再次清除已經到 RQ的弱引用removeWeaklyReachableReferences();// 如果此時對象還沒有到隊列,預期被 gc 的對象會出現在隊列中,沒出現則此時已經可能泄漏if (!gone(reference)) {long startDumpHeap = System.nanoTime();long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);// dump出來heap,此時認為內存確實已經泄漏了File heapDumpFile = heapDumper.dumpHeap();if (heapDumpFile == HeapDumper.NO_DUMP) {// 如果不能dump出內存則abortreturn;}long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);// 去分析heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,gcDurationMs, heapDumpDurationMs));}}//... } public interface GcTrigger {GcTrigger DEFAULT = new GcTrigger() {@Override public void runGc() {// Code taken from AOSP FinalizationTest:// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/// java/lang/ref/FinalizationTester.java// System.gc() does not garbage collect every time. Runtime.gc() is// more likely to perfom a gc.Runtime.getRuntime().gc();enqueueReferences();System.runFinalization();}private void enqueueReferences() {// Hack. We don't have a programmatic way to wait for the reference queue daemon to move// references to the appropriate queues.try {Thread.sleep(100);} catch (InterruptedException e) {throw new AssertionError();}}};void runGc(); }HeapDumper#dumpHeap
@Override public File dumpHeap() {if (!leakDirectoryProvider.isLeakStorageWritable()) {CanaryLog.d("Could not write to leak storage to dump heap.");leakDirectoryProvider.requestWritePermissionNotification();return NO_DUMP;}File heapDumpFile = getHeapDumpFile();// Atomic way to check for existence & create the file if it doesn't exist.// Prevents several processes in the same app to attempt a heapdump at the same time.boolean fileCreated;try {fileCreated = heapDumpFile.createNewFile();} catch (IOException e) {cleanup();CanaryLog.d(e, "Could not check if heap dump file exists");return NO_DUMP;}// 如果沒有目錄存放,則拋出if (!fileCreated) {CanaryLog.d("Could not dump heap, previous analysis still is in progress.");// Heap analysis in progress, let's not put too much pressure on the device.return NO_DUMP;}FutureResult<Toast> waitingForToast = new FutureResult<>();showToast(waitingForToast);if (!waitingForToast.wait(5, SECONDS)) {CanaryLog.d("Did not dump heap, too much time waiting for Toast.");return NO_DUMP;}Toast toast = waitingForToast.get();try {Debug.dumpHprofData(heapDumpFile.getAbsolutePath());cancelToast(toast);return heapDumpFile;} catch (Exception e) {cleanup();CanaryLog.d(e, "Could not perform heap dump");// Abort heap dumpreturn NO_DUMP;}} heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,gcDurationMs, heapDumpDurationMs)); public abstract class AbstractAnalysisResultService extends IntentServiceHeapAnalyzerService
public final class HeapAnalyzerService extends IntentService {//...public static void runAnalysis(Context context, HeapDump heapDump,Class<? extends AbstractAnalysisResultService> listenerServiceClass,String processType) {//...}// 重點@Override protected void onHandleIntent(Intent intent) {if (intent == null) {CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");return;}String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);String processType = intent.getStringExtra(PROCESS_TYPE);String processName = intent.getStringExtra(PROCESS_NAME);HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);// 使用checkForLeak這個方法來分析內存使用結果AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);// 結果回調AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result,processType,processName);} }HeapAnalyzer#checkForleak
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {long analysisStartNanoTime = System.nanoTime();if (!heapDumpFile.exists()) {Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);return failure(exception, since(analysisStartNanoTime));}try {// 將 dump 文件解析成 snapshot 對象,haha庫的用法HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);HprofParser parser = new HprofParser(buffer);Snapshot snapshot = parser.parse();deduplicateGcRoots(snapshot);Instance leakingRef = findLeakingReference(referenceKey, snapshot);// False alarm, weak reference was cleared in between key check and heap dump.if (leakingRef == null) {return noLeak(since(analysisStartNanoTime));}// 找到泄漏路徑return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);} catch (Throwable e) {return failure(e, since(analysisStartNanoTime));}} private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,Instance leakingRef) {ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);// False alarm, no strong reference path to GC Roots.if (result.leakingNode == null) {return noLeak(since(analysisStartNanoTime));}LeakTrace leakTrace = buildLeakTrace(result.leakingNode);String className = leakingRef.getClassObj().getClassName();// Side effect: computes retained size.snapshot.computeDominators();Instance leakingInstance = result.leakingNode.instance;long retainedSize = leakingInstance.getTotalRetainedSize();retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);// 即將跳轉到haha這個庫去建立最短引用路徑return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,since(analysisStartNanoTime));}AbstractAnalysisResultService
public abstract class AbstractAnalysisResultService extends IntentService {public static void sendResultToListener(Context context, String listenerServiceClassName,HeapDump heapDump, AnalysisResult result,String processType,String processName) {Class<?> listenerServiceClass;try {listenerServiceClass = Class.forName(listenerServiceClassName);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}Intent intent = new Intent(context, listenerServiceClass);intent.putExtra(HEAP_DUMP_EXTRA, heapDump);intent.putExtra(RESULT_EXTRA, result);intent.putExtra(PROCESS_TYPE,processType);intent.putExtra(PROCESS_NAME,processName);context.startService(intent);}public AbstractAnalysisResultService() {super(AbstractAnalysisResultService.class.getName());}@Override protected final void onHandleIntent(Intent intent) {HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);String process = intent.getStringExtra(PROCESS_TYPE);String processName = intent.getStringExtra(PROCESS_NAME);boolean delFile = true;try {delFile = onHeapAnalyzed(heapDump, result, process, processName);} finally {//noinspection ResultOfMethodCallIgnoredif (delFile)heapDump.heapDumpFile.delete();}}protected abstract boolean onHeapAnalyzed(HeapDump heapDump, AnalysisResult result, String processType, String processName); }09.DisplayLeakService
public class DisplayLeakService extends AbstractAnalysisResultService {@Overrideprotected final boolean onHeapAnalyzed(HeapDump heapDump, AnalysisResult result, String processType, String processName) {return onCustomHeapAnalyzed(heapDump, result, processType, processName);if (PROCESS_CLIENT.equals(processType)) {boolean resultSaved = false;boolean shouldSaveResult = result.leakFound || result.failure != null;if (shouldSaveResult) {resultSaved = saveResult(heapDump, result);if (result.failure != null) {File resultFile = new File(heapDump.heapDumpFile.getParentFile(), "analysis_fail" + ".bt");saveLeakInfo(heapDump, resultFile, leakInfo);}} else {//無泄露結果File resultFile = new File(heapDump.heapDumpFile.getParentFile(), "no_leakinfo" + ".db");saveLeakInfo(heapDump, resultFile, leakInfo);}Intent pendingIntent = null;if (resultSaved) {// DisplayLeakActivity顯示工作pendingIntent = DisplayLeakActivity.createIntent(this,heapDump.referenceKey, "","","");}String key = heapDump.referenceKey;afterClientHandling(heapDump, result, pendingIntent, key);return false;} else {// SERCERheapDump = renameHeapdump(heapDump);afterServerHandling(heapDump);}return true;}private boolean saveResult(HeapDump heapDump, AnalysisResult result) {File resultFile = new File(heapDump.heapDumpFile.getParentFile(),heapDump.heapDumpFile.getName() + ".result");FileOutputStream fos = null;try {fos = new FileOutputStream(resultFile);ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(heapDump);oos.writeObject(result);return true;} catch (IOException e) {CanaryLog.d(e, "Could not save leak analysis result to disk.");} finally {if (fos != null) {try {fos.close();} catch (IOException ignored) {}}}return false;}// 擴展式函數protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo, String processType) {}protected void afterClientHandling(HeapDump heapDump, AnalysisResult result, Intent intent, String key) {}protected void afterServerHandling(HeapDump heapDump) {}protected void afterAnalyzed(PushServiceTaskParam pushTaskParam) {} } public static boolean isInServiceProcess(Context context, Class<? extends Service> serviceClass) {PackageManager packageManager = context.getPackageManager();PackageInfo packageInfo;try {packageInfo = packageManager.getPackageInfo(context.getPackageName(), GET_SERVICES);} catch (Exception e) {CanaryLog.d(e, "Could not get package info for %s", context.getPackageName());return false;}String mainProcess = packageInfo.applicationInfo.processName;ComponentName component = new ComponentName(context, serviceClass);ServiceInfo serviceInfo;try {serviceInfo = packageManager.getServiceInfo(component, 0);} catch (PackageManager.NameNotFoundException ignored) {// Service is disabled.return false;}// 注意這里if (serviceInfo.processName.equals(mainProcess)) {CanaryLog.d("Did not expect service %s to run in main process %s", serviceClass, mainProcess);// Technically we are in the service process, but we're not in the service dedicated process.return false;}// 為PID找到對應的Processint myPid = android.os.Process.myPid();ActivityManager activityManager =(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);ActivityManager.RunningAppProcessInfo myProcess = null;List<ActivityManager.RunningAppProcessInfo> runningProcesses =activityManager.getRunningAppProcesses();if (runningProcesses != null) {for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {if (process.pid == myPid) {myProcess = process;break;}}}if (myProcess == null) {CanaryLog.d("Could not find running process for %d", myPid);return false;}return myProcess.processName.equals(serviceInfo.processName);}總結
- 上一篇: 图灵奖得主Judea Pearl:最近值
- 下一篇: 闲云孤鹤