从KOOM看java内存泄漏检测

前面我们了解了LeakCanary和Matrix Resource Canary中内存泄漏的监控和解析,不难看出LeakCanary是只能在线下部署的,主要原因是因为Debug.dumpHprofData执行会冻结整个应用进程,造成应用进程几秒乃至十多秒不能响应的情况,而dump时机有可能比较频繁,所以不能线上部署。

Matrix虽然提供了子进程生成hprof文件的能力,但其对hprof文件的处理比较简单,虽然经过压缩,但是单个hprof文件仍然很大,在hprof文件上传至后台的过程中,对用户流量有大量消耗,所以整体来讲也不建议在线上集成,那么就没有线上可以进行内存泄漏监控的开源库了吗?

当然有,这就是快手开源的KOOM框架,其有以下显著优势:

  1. KOOM中在子进程执行dump文件的生成,避免造成应用进程冻结,影响用户体验,在子进程进行dump文件生成对应用进程的影响基本可以忽略不计
  2. 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模块的使用总体而言分两步:

  1. 通过MonitorManager.addMonitorConfig添加OOMMonitorConfig对象,配置OOMMonitor的基础设置
  2. 调用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主要经历四个阶段:

  1. createJsonAnalysisFile
  2. createHprofAnalysisFile
  3. ForkJvmHeapDumper.dump
  4. 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实现,分三个阶段:

  1. buildIndex(创建HeapGraph)
  2. filterLeakingObjects
  3. 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查找,主要规则如下:

  1. 已经destroyed和finished的activity
  2. 已经fragment manager为空的fragment
  3. 已经destroyed的window
  4. 超过阈值大小的bitmap
  5. 超过阈值大小的基本类型数组
  6. 超过阈值大小的对象个数的任意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)
}

koom-java-leak架构

相关推荐
小镇学者6 小时前
【PHP】导入excel 报错Trying to access array offset on value of type int
android·php·excel
阿芯爱编程8 小时前
最长和谐子序列,滑动窗口
前端·javascript·面试
一笑的小酒馆9 小时前
Android11 Launcher3去掉抽屉改为单层
android
拉不动的猪10 小时前
JQ常规面试题
前端·javascript·面试
louisgeek11 小时前
Git 根据不同目录设置不同账号
android
qq_3909347412 小时前
MySQL中的系统库(简介、performance_schema)
android·数据库·mysql
whysqwhw12 小时前
Kotlin Flow 实现响应式编程指南
android
二流小码农13 小时前
鸿蒙开发:一文了解桌面卡片
android·ios·harmonyos
每次的天空13 小时前
Android第十七次面试总结(Java数据结构)
android·java·面试
梁同学与Android13 小时前
Android --- Handler的用法,子线程中怎么切线程进行更新UI
android·handler·子线程更新ui·切换到主线程