从KOOM看Java内存泄漏检测

news2024/11/30 8:32:30

前面我们了解了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

示例代码如下:

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
// 参数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,实现代码如下:

image-20230831224658711

从代码中可以看出startLoop的本质是向消息队列里面添加了mLoopRunnable消息,该消息执行时会执行OOMMonitor的call方法,call方法中通过trackOOM进行内存泄漏检测,当trackOOM方法返回LoopState.Terminate时停止检测。

OOMMonitor.trackOOM
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代码实现如下:

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

代码内容比较简单,不做赘述,详细代码如下:

@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
@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 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文件分析流程,代码如下所示:

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)

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是按照规则查找泄漏对象,查找代码如下:

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,代码如下:

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

KOOM-leak-java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/986664.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

异步编程 - 10 Web Servlet的异步非阻塞处理

文章目录 OverViewServlet概述Servlet 3.0提供的异步处理能力Servlet 3.1提供的非阻塞IO能力Spring Web MVC的异步处理能力基于DeferredResult的异步处理基于Callable实现异步处理 小结 OverView 我们这里主要讨论Servlet3.0规范前的同步处理模型和缺点&#xff0c;Servlet3.0…

Amazon Aurora MySQL 和 Amazon RDS for MySQL 集群故障转移和只读实例扩容时间测试

01 测试背景 Amazon Aurora MySQL 是与 MySQL 兼容的关系数据库&#xff0c;专为云而打造&#xff0c;性能和可用性与商用数据库相当&#xff0c;成本只有其 1/10。 Amazon RDS for MySQL 让您能够在云中更轻松设置、操作和扩展 MySQL 部署。借助 Amazon RDS&#xff0c;您可以…

小白备战大厂算法笔试(三)——栈、队列、双向队列

文章目录 栈栈常用操作栈的实现基于链表的实现基于数组的实现 两种实现对比栈典型应用 队列队列常用操作队列实现基于链表的实现基于数组的实现 队列典型应用 双向队列双向队列常用操作双向队列实现基于双向链表的实现基于数组的实现 双向队列应用 栈 栈是一种遵循先入后出的逻…

Java从入门到精通-流程控制(二)

习题讲解&#xff1a; 上次我们给大家留了一些流程控制的问题&#xff0c;这次给大家分析讲解一下&#xff1a; 条件语句练习&#xff1a; 1.编写Java程序&#xff0c;用于接受用户输入的数字&#xff0c;然后判断它是偶数还是奇数&#xff0c;并输出相应的消息。 import ja…

电表采集器是如何接线的?

随着社会的进步和科技的发展&#xff0c;智能化和自动化已经成为了各个行业的发展趋势。在电力系统领域&#xff0c;电表采集器的应用越来越广泛&#xff0c;它实现了电能数据的远程采集、传输和分析&#xff0c;为电力系统的稳定运行提供了重要的数据支持。那么&#xff0c;电…

广东成人高考报名将于9月14日开始!

截图来自广东省教育考试院官网* 今年的广东成人高考正式报名时间终于确定了&#xff01; 报名时间&#xff1a;2023年 9 月14—20日 准考证打印时间&#xff1a;考前一周左右 考试时间&#xff1a;2023年10月21—22日 录取时间&#xff1a;2023年12 月中上旬 报名条件: …

恒运资本:存储市场有望触底反弹 电子竞技迎催化

昨日&#xff0c;沪指早盘震动下探&#xff0c;午后拉升翻红&#xff1b;深成指、创业板指跌幅收窄&#xff1b;到收盘&#xff0c;沪指涨0.12%报3158.08点&#xff0c;深成指跌0.24%报10515.21点&#xff0c;创业板指跌0.47%报2101.4点&#xff0c;科创50指数涨0.43%&#xff…

集成快递物流平台(快递100、快递鸟、闪送)连通多个应用

场景描述&#xff1a; 基于快递物流平台&#xff08;快递100、快递鸟、闪送等&#xff09;开放能力&#xff0c;无代码集成快递物流平台与多个应用互连互通。通过Aboter可搭建业务自动化流程&#xff0c;实现多个应用之间的数据连接。 连接器&#xff1a; 快递100快递鸟闪送…

容器编排学习(五)卷的概述与存储卷管理

一 卷 1 容器化带来的问题 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行的重要的应用程序带来一些问题 问题1&#xff1a;当容器崩溃或重启的时候&#xff0c;kubelet 会以干净的状态(镜像的状态)重启容器&#xff0c;容器内的历史数据会丢失 问题2&…

Mediasoup本地部署

一、环境 1、nodejs、npm、mac、ssh 注意:不能使用python3&#xff0c;请用自带的python 二、代码下载 1、mediasoup-demo 包含app(客户端)、server(服务端)、broadcasters(推流模块) git clone https://github.com/versatica/mediasoup-demo.git git checkout v3 2、me…

RC-u3 兰州拉面派餐系统--睿抗2023国赛

兰州拉面是著名美食&#xff0c;其煮面很有讲究&#xff0c;不同种类的面需要煮不同的时长。拉面馆的煮面师傅的规则很简单&#xff0c;只要手头有煮面篮子是空闲的&#xff0c;就把下一份客单指定的面放到空闲篮子里煮&#xff1b;如果空闲的篮子不止一个&#xff0c;那么先放…

无涯教程-JavaScript - BITLSHIFT函数

描述 BITLSHIFT函数返回一个左移指定位数的数字。 语法 BITLSHIFT (number, shift_amount)争论 Argument描述Required/OptionalnumberNumber must be an integer greater than or equal to 0.Requiredshift_amountShift_amount must be an integer.Required Notes 向左移动…

Zenlayer 软件定义网络平台赋能海底光缆服务

上海&#xff0c;2023年9月6日&#xff0c;一年一度的印尼电信国际大会——Batic在巴厘岛隆重举行。该盛会云集了亚太地区电信及相关行业的重要领导者&#xff0c;就“共塑亚太地区数字化未来”进行了深入探讨。Zenlayer作为该会议的黄金赞助商&#xff0c;宣布将在原有L2、L3网…

Science adv | 转录因子SPIC连接胚胎干细胞中的细胞代谢与表观调控

代谢是生化反应网络的结果&#xff0c;这些反应吸收营养物质并对其进行处理&#xff0c;以满足细胞的需求&#xff0c;包括能量产生和生物合成。反应的中间体被用作各种表观基因组修饰酶的底物和辅助因子&#xff0c;因此代谢与表观遗传密切相关。代谢结合表观遗传涉及疾病&…

【特殊文本文件——Properties和xml文件】

特殊文本文件 一、Properties 是一个Map集合&#xff08;键值对集合&#xff09;&#xff0c;但是我们一般不会当集合用核心作用&#xff1a;Properties是用来代表属性文件的&#xff0c;通过Properties可以读写属性文件里的内容 1.使用Properties读取属性文件里的键值对数据…

基于51单片机烟雾温度检测报警系统设计

一、系统方案 本设计采用52单片机作为主控器&#xff0c;液晶1602显示&#xff0c;DS18B20采集温度&#xff0c;MQ2采集烟雾值&#xff0c;火焰传感器&#xff0c;按键设置报警&#xff0c;声光报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系…

MS-TTS:免费微软TTS语音合成工具(一键合成导出MP3音频)

声明 本工具是个免费工具&#xff0c;遇到问题&#xff0c;还请自行解决&#xff0c;下面有文字教程&#xff0c;B站有视频教程&#xff08;链接在文章末尾&#xff09;&#xff1b; 其次&#xff0c;微软接口卡顿&#xff0c;连接超时等问题下方有详细说明&#xff0c;请仔细…

视频怎么制作动图?分享简单的视频制作gif方法

现在的人们常用视频来记录自己的生活&#xff0c;但是视频的体积都会比较大&#xff0c;保存传输非常的不方便。我们可以将视频制作成gif动图来使用&#xff0c;而且gif动图也能将自己的心情想表达的想法通过这种个性的方式展示给对方。接下来&#xff0c;就给大家分享一款视频…

雅思 四处刷题的记录(一)

目录 阅读 九分达人7 test2 p2 听力 剑18 test1 section 1 剑18 test1 section 2 《九分达人》6 test3 section 1 and section 2 阅读 《九分达人》7 test2 passage1 词汇 Travelling的含义 town跟介词在一起的搭配 什么情况下使用on 大作文写作思路 当我们要使…

Python自动化测试(超详细~)

1. 概述 1.1 python自动化 什么是python自动化&#xff1f;我理解的Python自动化测试就是把以前人为测试转化为机器测试的一种过程。自动化测试是一种比手工测试更快获得故障反馈的方法。 自动化测试是一种质量保障的方式&#xff0c;最重要的还是以做好一款高质量产品为前提…