从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架构

相关推荐
姑苏风3 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
IT规划师6 小时前
开源 - Ideal库 - 常用时间转换扩展方法(二)
开源·.net core·时间转换·ideal库
数据猎手小k6 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小107 小时前
JavaWeb项目-----博客系统
android
_tison7 小时前
夜天之书 #103 开源嘉年华纪实
开源
customer087 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
风和先行8 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.8 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
测试19989 小时前
2024软件测试面试热点问题
自动化测试·软件测试·python·测试工具·面试·职场和发展·压力测试
似霰9 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder