前面我们了解了LeakCanary和Matrix Resource Canary中内存泄漏的监控和解析,不难看出LeakCanary是只能在线下部署的,主要原因是因为Debug.dumpHprofData执行会冻结整个应用进程,造成应用进程几秒乃至十多秒不能响应的情况,而dump时机有可能比较频繁,所以不能线上部署。
Matrix虽然提供了子进程生成hprof文件的能力,但其对hprof文件的处理比较简单,虽然经过压缩,但是单个hprof文件仍然很大,在hprof文件上传至后台的过程中,对用户流量有大量消耗,所以整体来讲也不建议在线上集成,那么就没有线上可以进行内存泄漏监控的开源库了吗?
当然有,这就是快手开源的KOOM框架,其有以下显著优势:
- KOOM中在子进程执行dump文件的生成,避免造成应用进程冻结,影响用户体验,在子进程进行dump文件生成对应用进程的影响基本可以忽略不计
- KOOM基于shark深度定制了hprof文件的裁剪流程,在native hook hprof文件生成,边生成边裁剪,处理后的hprof文件大小压缩后达到3M左右
KOOM框架主要包含以下功能:
-
Java Heap 泄漏监控
koom-java-leak
模块用于 Java Heap 泄漏监控:它利用 Copy-on-write 机制 fork 子进程 dump Java Heap,解决了 dump 过程中 app 长时间冻结的问题 -
Native Heap 泄漏监控
koom-native-leak
模块用于 Native Heap 泄漏监控:它利用 Tracing garbage collection 机制分析整个 Native Heap,直接输出泄漏内存信息「大小、分配堆栈等』;极大的降低了业务同学分析、解决内存泄漏的成本。 -
Thread 泄漏监控
koom-thread-leak
模块用于 Thread 泄漏监控:它会 hook 线程的生命周期函数,周期性的上报泄漏线程信息。
Java Heap 泄漏监控
KOOM中Java Heap泄漏监控的实现在koom-java-leak模块,不同与LeakCanary和Matrix Resource Canary,koom-java-leak模块实现的内存泄漏监控并不是通过弱引用或者ReferenceQueue来实现的,而是通过检测内存大小的变化来实现的,如果多次检测内存仍然处于逐步增长状态或者超过预定阈值,则会触发内存分析,进行内存泄漏检测。
koom-java-leak模块的使用
koom-java-leak模块的使用总体而言分两步:
- 通过MonitorManager.addMonitorConfig添加OOMMonitorConfig对象,配置OOMMonitor的基础设置
- 调用OOMMonitor.startLoop方法启动监控即可
MonitorManager.addMonitorConfig
示例代码如下:
kotlin
val config = OOMMonitorConfig.Builder()
.setThreadThreshold(50) //线程增量阈值
.setFdThreshold(300) // fd增量阈值
.setHeapThreshold(0.9f) // 堆内存使用比例
.setVssSizeThreshold(1_000_000) // VSS内存阈值,单位kb
.setMaxOverThresholdCount(1) // 超过最大次数阈值
.setAnalysisMaxTimesPerVersion(3) // 每个版本最多分析次数
.setAnalysisPeriodPerVersion(15 * 24 * 60 * 60 * 1000) // 每个版本的前15天才分析,超过这个时间段不再dump
.setLoopInterval(5_000) // 检测的间隔时间
.setEnableHprofDumpAnalysis(true)
.setHprofUploader(object : OOMHprofUploader {
override fun upload(file: File, type: OOMHprofUploader.HprofType) {
MonitorLog.e("OOMMonitor", "todo, upload hprof ${file.name} if necessary")
}
})
.setReportUploader(object : OOMReportUploader {
override fun upload(file: File, content: String) {
MonitorLog.i("OOMMonitor", content)
MonitorLog.e("OOMMonitor", "todo, upload report ${file.name} if necessary")
}
})
.build()
MonitorManager.addMonitorConfig(config)
OOMMonitor.startLoop
arduino
// 参数1 clearQueue-true则删除单例对象中已存在的消息
// 参数2 postAtFront-true则添加到队列首位
// 参数3 delayMillis-延时时间
OOMMonitor.INSTANCE.startLoop(true, false,5_000L);
由于OOMMonitor是单例对象,所以可以直接通过INSTANCE成员调用。
OOMMonitor实现原理
OOMMonitor.startLoop
从koom-java-leak模块使用可以看出,整个监控逻辑的起点是OOMMonitor.startLoop,实现代码如下:
从代码中可以看出startLoop的本质是向消息队列里面添加了mLoopRunnable消息,该消息执行时会执行OOMMonitor的call方法,call方法中通过trackOOM进行内存泄漏检测,当trackOOM方法返回LoopState.Terminate时停止检测。
OOMMonitor.trackOOM
scss
private val mOOMTrackers = mutableListOf(HeapOOMTracker(), ThreadOOMTracker(), FdOOMTracker(),PhysicalMemoryOOMTracker(), FastHugeMemoryOOMTracker()
)
private fun trackOOM(): LoopState {
SystemInfo.refresh()
mTrackReasons.clear()
for (oomTracker in mOOMTrackers) {
if (oomTracker.track()) {
mTrackReasons.add(oomTracker.reason())
}
}
if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {
if (isExceedAnalysisPeriod() || isExceedAnalysisTimes()) {
MonitorLog.e(TAG, "Triggered, but exceed analysis times or period!")
} else {
async {
MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")
dumpAndAnalysis()
}
}
return LoopState.Terminate
}
return LoopState.Continue
}
通过trackOOM代码可以看到,这里主要是通过遍历mOOMTrackers中的对象,执行每一个对象的track方法,如果其中有一个发现了问题,则执行dumpAndAnalysis方法,终止检测流程,否则继续检测。
在mOOMTrackers中主要包含5个对象,他们的作用如下所示:
- HeapOOMTracker:对堆内存进行OOM检查,当堆内存占用率超过指定阈值并且每次增长超过阈值阈值,达到指定次数时触发。
- ThreadOOMTracker:针对线程数量进行OOM检查,当线程数量超过指定阈值且单次增长超过增量阈值,达到指定次数时触发。
- FdOOMTracker:针对Fd数量进行OOM检查,当fd数量超过指定阈值且单次增长超过增量阈值,达到指定次数时触发。
- PhysicalMemoryOOMTracker:针对物理内存进行OOM检查,目前只有相关比例日志打印,并不会触发内存泄漏检查。
- FastHugeMemoryOOMTracker:已用内存达到阈值或者两次可用内存差超过阈值时触发检查,默认是可用内存达到最大可用内存的90%或者当前可用内存减去上次可用内存大于350000KB。
dumpAndAnalysis
dumpAndAnalysis代码实现如下:
kotlin
private fun dumpAndAnalysis() {
MonitorLog.i(TAG, "dumpAndAnalysis");
runCatching {
if (!OOMFileManager.isSpaceEnough()) {
MonitorLog.e(TAG, "available space not enough", true)
return@runCatching
}
if (mHasDumped) {
return
}
mHasDumped = true
val date = Date()
//创建解析结果的json文件
val jsonFile = OOMFileManager.createJsonAnalysisFile(date)
//创建hprof文件
val hprofFile = OOMFileManager.createHprofAnalysisFile(date).apply {
createNewFile()
setWritable(true)
setReadable(true)
}
MonitorLog.i(TAG, "hprof analysis dir:$hprofAnalysisDir")
//子进程生成hprof文件内容
ForkJvmHeapDumper.getInstance().run {
dump(hprofFile.absolutePath)
}
MonitorLog.i(TAG, "end hprof dump", true)
Thread.sleep(1000) // 等待文件同步完成.
MonitorLog.i(TAG, "start hprof analysis")
//开始分析hprof文件
startAnalysisService(hprofFile, jsonFile, mTrackReasons.joinToString())
}.onFailure {
it.printStackTrace()
MonitorLog.i(TAG, "onJvmThreshold Exception " + it.message, true)
}
}
可以看到在dumpAndAnalysis主要经历四个阶段:
- createJsonAnalysisFile
- createHprofAnalysisFile
- ForkJvmHeapDumper.dump
- startAnalysisService
createJsonAnalysisFile和createHprofAnalysisFile
代码内容比较简单,不做赘述,详细代码如下:
kotlin
@JvmStatic
fun createHprofAnalysisFile(date: Date): File {
val time = SimpleDateFormat(TIME_FORMAT, Locale.CHINESE).format(date)
return File(hprofAnalysisDir, "$mPrefix$time.hprof").also {
hprofAnalysisDir.mkdirs()
}
}
@JvmStatic
fun createJsonAnalysisFile(date: Date): File {
val time = SimpleDateFormat(TIME_FORMAT, Locale.CHINESE).format(date)
return File(hprofAnalysisDir, "$mPrefix$time.json").also {
hprofAnalysisDir.mkdirs()
}
}
ForkJvmHeapDumper.dump
java
@Override
public synchronized boolean dump(String path) {
MonitorLog.i(TAG, "dump " + path);
if (!sdkVersionMatch()) {
throw new UnsupportedOperationException("dump failed caused by sdk version not supported!");
}
init();
if (!mLoadSuccess) {
MonitorLog.e(TAG, "dump failed caused by so not loaded!");
return false;
}
boolean dumpRes = false;
try {
MonitorLog.i(TAG, "before suspend and fork.");
// 父进程阻塞并创建子进程
int pid = suspendAndFork();
if (pid == 0) {
// 子进程生成hprof文件数据
Debug.dumpHprofData(path);
// 退出子进程
exitProcess();
} else if (pid > 0) {
// 父进程唤醒等待子进程处理结果
dumpRes = resumeAndWait(pid);
MonitorLog.i(TAG, "dump " + dumpRes + ", notify from pid " + pid);
}
} catch (IOException e) {
MonitorLog.e(TAG, "dump failed caused by " + e);
e.printStackTrace();
}
return dumpRes;
}
private native int suspendAndFork();
private native boolean resumeAndWait(int pid);
private native void exitProcess();
结合代码可以看出koom中子进程fork的实现与Matrix中实现基本一致,详细信息可参考从Matrix-ResourceCanary看内存快照生成-ForkAnalyseProcessor,只不过Matrix在底层通过xhook调用系统的native接口,koom通过kwai-linker组件,详细代码可以查看hprof_dump.cpp
startAnalysisService
startAnalysisService中代码主要是携带hprof文件相关参数,通过startService启动HeapAnalysisService这个服务来进行hprof文件的解析工作,HeapAnalysisService继承自IntentService,该服务声明如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.kwai.koom.javaoom"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<service
android:name=".monitor.analysis.HeapAnalysisService"
android:process=":heap_analysis" />
</application>
</manifest>
可以看到该Service运行在heap_analysis进程中。
启动HeapAnalysisService后在其onHandleIntent中接收数据并执行hprof文件分析流程,代码如下所示:
kotlin
override fun onHandleIntent(intent: Intent?) {
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 {
// shark创建HeapGraph
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
}
// 初始化解析结果json文件
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 {
// 找到内存泄漏对象的GC Root path
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
}
// 填充解析结果到json文件中
fillJsonFile(jsonFile)
resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK, null)
// 退出进程
System.exit(0);
}
从代码可以看出整个解析过程使用shark实现,分三个阶段:
- buildIndex(创建HeapGraph)
- filterLeakingObjects
- findPathsToGcRoot
buildIndex(创建HeapGraph)
kotlin
private fun buildIndex(hprofFile: String?) {
if (hprofFile.isNullOrEmpty()) return
MonitorLog.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 {
// 根据指定GC Root类型创建HeapGraph
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 {
MonitorLog.i(TAG, "build index cost time: $it")
}
}
filterLeakingObjects
filterLeakingObjects是按照规则查找泄漏对象,查找代码如下:
ini
private fun filterLeakingObjects() {
val startTime = System.currentTimeMillis()
MonitorLog.i(TAG, "filterLeakingObjects " + Thread.currentThread())
val activityHeapClass = mHeapGraph.findClassByName(ACTIVITY_CLASS_NAME)
val fragmentHeapClass = mHeapGraph.findClassByName(ANDROIDX_FRAGMENT_CLASS_NAME)
?: mHeapGraph.findClassByName(NATIVE_FRAGMENT_CLASS_NAME)
?: mHeapGraph.findClassByName(SUPPORT_FRAGMENT_CLASS_NAME)
val bitmapHeapClass = mHeapGraph.findClassByName(BITMAP_CLASS_NAME)
val nativeAllocationHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLASS_NAME)
val nativeAllocationThunkHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME)
val windowClass = mHeapGraph.findClassByName(WINDOW_CLASS_NAME)
//缓存classHierarchy,用于查找class的所有instance
val classHierarchyMap = mutableMapOf<Long, Pair<Long, Long>>()
//记录class objects数量
val classObjectCounterMap = mutableMapOf<Long, ObjectCounter>()
//遍历镜像的所有instance
for (instance in mHeapGraph.instances) {
if (instance.isPrimitiveWrapper) {
continue
}
//使用HashMap缓存及遍历两边classHierarchy,这2种方式加速查找instance是否是对应类实例
//superId1代表类的继承层次中倒数第一的id,0就是继承自object
//superId4代表类的继承层次中倒数第四的id
//类的继承关系,以AOSP代码为主,部分厂商入如OPPO Bitmap会做一些修改,这里先忽略
val instanceClassId = instance.instanceClassId
val (superId1, superId4) = if (classHierarchyMap[instanceClassId] != null) {
classHierarchyMap[instanceClassId]!!
} else {
val classHierarchyList = instance.instanceClass.classHierarchy.toList()
val first = classHierarchyList.getOrNull(classHierarchyList.size - 2)?.objectId ?: 0L
val second = classHierarchyList.getOrNull(classHierarchyList.size - 5)?.objectId ?: 0L
Pair(first, second).also { classHierarchyMap[instanceClassId] = it }
}
//Activity
if (activityHeapClass?.objectId == superId4) {
val destroyField = instance[ACTIVITY_CLASS_NAME, DESTROYED_FIELD_NAME]!!
val finishedField = 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)
}
}
continue
}
//Fragment
if (fragmentHeapClass?.objectId == superId1) {
val fragmentManager = instance[fragmentHeapClass.name, FRAGMENT_MANAGER_FIELD_NAME]
if (fragmentManager != null && fragmentManager.value.asObject == null) {
val mCalledField = instance[fragmentHeapClass.name, FRAGMENT_MCALLED_FIELD_NAME]
//mCalled为true且fragment manager为空时认为fragment已经destroy
val isLeak = mCalledField != null && mCalledField.value.asBoolean!!
val 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
}
//Bitmap
if (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)
//加入大对象泄露json
val leakObject = HeapReport.LeakObject().apply {
className = instance.instanceClassName
size = (width * height).toString()
extDetail = "$width x $height"
objectId = (instance.objectId and 0xffffffffL).toString()
}
mLeakModel.leakObjects.add(leakObject)
}
}
continue
}
//nativeallocation/NativeAllocationThunk/window
if (nativeAllocationHeapClass?.objectId == superId1
|| nativeAllocationThunkHeapClass?.objectId == superId1
|| windowClass?.objectId == superId1) {
updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, false)
}
}
//关注class和对应instance数量,加入json
for ((instanceId, objectCounter) in classObjectCounterMap) {
val leakClass = HeapReport.ClassInfo().apply {
val heapClass = mHeapGraph.findObjectById(instanceId).asClass
className = heapClass?.name
instanceCount = objectCounter.allCnt.toString()
MonitorLog.i(OOM_ANALYSIS_TAG, "leakClass.className: $className leakClass.objectCount: $instanceCount")
}
mLeakModel.classInfos.add(leakClass)
}
//查找基本类型数组
val primitiveArrayIterator = mHeapGraph.primitiveArrays.iterator()
while (primitiveArrayIterator.hasNext()) {
val primitiveArray = primitiveArrayIterator.next()
val arraySize = primitiveArray.recordSize
if (arraySize >= DEFAULT_BIG_PRIMITIVE_ARRAY) {
val arrayName = primitiveArray.arrayClassName
val 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 = arrayName
size = 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.recordSize
if (arraySize >= DEFAULT_BIG_OBJECT_ARRAY) {
val arrayName = objectArray.arrayClassName
MonitorLog.i(OOM_ANALYSIS_TAG,
"object arrayName:" + arrayName + " objectId:" + objectArray.objectId)
mLeakingObjectIds.add(objectArray.objectId)
val leakObject = HeapReport.LeakObject().apply {
className = arrayName
size = 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)
}
遍历HeapGraph中所有class查找,主要规则如下:
- 已经destroyed和finished的activity
- 已经fragment manager为空的fragment
- 已经destroyed的window
- 超过阈值大小的bitmap
- 超过阈值大小的基本类型数组
- 超过阈值大小的对象个数的任意class
findPathsToGcRoot
根据上一步中查找到的mLeakingObjectIds中包含的所有对象,查找对象的GC Root path,代码如下:
kotlin
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(mHeapGraph, AndroidReferenceMatchers.appDefaults,
false, mutableListOf())
val (applicationLeaks, libraryLeaks) = with(heapAnalyzer) {
findLeakInput.findLeaks(mLeakingObjectIds)
}
MonitorLog.i(OOM_ANALYSIS_TAG,
"---------------------------Application Leak---------------------------------------")
//填充application leak
MonitorLog.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 = gcRootType.description
val labels = 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.size
this.leakReason = leakTraceObject.leakingStatusReason
this.gcRoot = gcRoot
this.signature = applicationLeak.signature
}
.also { mLeakModel.gcPaths.add(it) }
// 添加索引到的trace path
for (reference in referencePath) {
val referenceName = reference.referenceName
val clazz = reference.originObject.className
val referenceDisplayName = reference.referenceDisplayName
val referenceGenericName = reference.referenceGenericName
val referenceType = reference.referenceType.toString()
val declaredClassName = reference.owningClassName
MonitorLog.i(OOM_ANALYSIS_TAG, "clazz:" + clazz +
", referenceName:" + referenceName
+ ", referenceDisplayName:" + referenceDisplayName
+ ", referenceGenericName:" + referenceGenericName
+ ", referenceType:" + referenceType
+ ", declaredClassName:" + declaredClassName)
val leakPathItem = HeapReport.GCPath.PathItem().apply {
this.reference = if (referenceDisplayName.startsWith("[")) //数组类型[]
clazz
else
"$clazz.$referenceDisplayName"
this.referenceType = referenceType
this.declaredClass = declaredClassName
}
leakTraceChainModel.path.add(leakPathItem)
}
// 添加本身trace path
leakTraceChainModel.path.add(HeapReport.GCPath.PathItem().apply {
reference = leakTraceObject.className
referenceType = leakTraceObject.typeName
})
}
MonitorLog.i(OOM_ANALYSIS_TAG, "=======================================================================")
MonitorLog.i(OOM_ANALYSIS_TAG, "----------------------------Library Leak--------------------------------------");
//填充library leak
MonitorLog.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 = gcRootType.description
val labels = 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().apply {
this.instanceCount = libraryLeak.leakTraces.size
this.leakReason = leakTraceObject.leakingStatusReason
this.signature = libraryLeak.signature
this.gcRoot = gcRoot
}
mLeakModel.gcPaths.add(leakTraceChainModel)
// 添加索引到的trace path
for (reference in referencePath) {
val clazz = reference.originObject.className
val referenceName = reference.referenceName
val referenceDisplayName = reference.referenceDisplayName
val referenceGenericName = reference.referenceGenericName
val referenceType = reference.referenceType.toString()
val declaredClassName = reference.owningClassName
MonitorLog.i(OOM_ANALYSIS_TAG, "clazz:" + clazz +
", referenceName:" + referenceName
+ ", referenceDisplayName:" + referenceDisplayName
+ ", referenceGenericName:" + referenceGenericName
+ ", referenceType:" + referenceType
+ ", declaredClassName:" + declaredClassName)
val leakPathItem = HeapReport.GCPath.PathItem().apply {
this.reference = if (referenceDisplayName.startsWith("["))
clazz
else //数组类型[]
"$clazz.$referenceDisplayName"
this.referenceType = referenceType
this.declaredClass = declaredClassName
}
leakTraceChainModel.path.add(leakPathItem)
}
// 添加本身trace path
leakTraceChainModel.path.add(HeapReport.GCPath.PathItem().apply {
reference = leakTraceObject.className
referenceType = 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)
}