内存优化之-Koom源码分析 - Java内存监控(一)

文章详细介绍了Koom库在Java层进行内存优化的过程,包括通过KoomDemo的JavaLeakTestActivity入口,分析OOMMonitor的初始化和循环检测,以及HeapOOMTracker、ThreadOOMTracker等不同类型的内存追踪器的工作原理。在内存达到特定阈值时,Koom会触发dumphprof进行内存快照,并通过HeapAnalysisService分析泄漏对象。最后,文章提到了LeakMaker如何模拟泄漏情况以进行测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java层泄漏

源码分析

1.koom-demo的JavaLeakTestActivity作为入口开始分析

JavaLeakTestActivity onClick方法
case R.id.btn_make_java_leak:
        showJavaLeakHint();

        /*
         * Init OOMMonitor
         */
        OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication());   //代码1,做一些初始化的配置
        OOMMonitor.INSTANCE.startLoop(true, false,5_000L);  //代码2

        /*
         * Make some leaks for test!
         */
        LeakMaker.makeLeak(this);
        break;

代码1做一些初始化的配置,代码2则是开启子线程进行轮训

2.继续分析上述两处代码

OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication()); 
OOMMonitor.INSTANCE.startLoop(true, false,5_000L);
OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication()); 
override fun init(application: Application) {
    val config = OOMMonitorConfig.Builder()	//代码3
      .setThreadThreshold(50) //50 only for test! Please use default value!
      .setFdThreshold(300) // 300 only for test! Please use default value!
      .setHeapThreshold(0.9f) // 0.9f for test! Please use default value!
      .setVssSizeThreshold(1_000_000) // 1_000_000 for test! Please use default value!
      .setMaxOverThresholdCount(1) // 1 for test! Please use default value!
      .setAnalysisMaxTimesPerVersion(3) // Consider use default value!
      .setAnalysisPeriodPerVersion(15 * 24 * 60 * 60 * 1000) // Consider use default value!
      .setLoopInterval(5_000) // 5_000 for test! Please use default value!
      .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)		//代码4
  }

代码3处设置一些初始化参数,以及回调(具体参数后边使用的时候会解释) ,调用build构建OOMMonitorConfig对象(OOMMonitorConfig继承自MonitorConfig)
具体分析下代码4处MonitorManager.addMonitorConfig(config)

fun <M : MonitorConfig<*>> addMonitorConfig(config: M) = apply {
    var supperType: Type? = config.javaClass.genericSuperclass
    while (supperType is Class<*>) {
      supperType = supperType.genericSuperclass
    }

    if (supperType !is ParameterizedType) {   // 代码5 判断是否是参数化类型
      throw java.lang.RuntimeException("config must be parameterized")
    }

    val monitorType = supperType.actualTypeArguments[0] as Class<Monitor<M>>

    if (MONITOR_MAP.containsKey(monitorType)) {
      return@apply
    }

    val monitor = try {   //代码6
      monitorType.getDeclaredField("INSTANCE").get(null) as Monitor<M>
    } catch (e: Throwable) {
      monitorType.newInstance() as Monitor<M>
    }

    MONITOR_MAP[monitorType] = monitor   //反射获取monitor对象,存储在map

    monitor.init(commonConfig, config).  //代码7

    monitor.logMonitorEvent()
  }

上述代码5,获取最上层的父类对象,判断是否是参数化对象
代码6获取范型对象的第一个参数,构造成对象,存储到MONITOR_MAP集合,并且调用对象的init进行初始化,即代码3构造的MonitorConfig对象,OOMMonitor

3.然后继续分析OOMMonitor.INSTANCE.startLoop(true, false,5_000L);

override fun startLoop(clearQueue: Boolean, postAtFront: Boolean, delayMillis: Long) {   //true false 5_000L
    throwIfNotInitialized { return }

    if (!isMainProcess()) {   //判断是否是主进程
      return
    }

    MonitorLog.i(TAG, "startLoop()")

    if (mIsLoopStarted) {
      return
    }
    mIsLoopStarted = true

    super.startLoop(clearQueue, postAtFront, delayMillis)   //代码8 子线程执行call
    getLoopHandler().postDelayed({ async { processOldHprofFile() } }, delayMillis)
  }

代码8则是在子线程轮训调用OOMMonitor.call()方法

OOMMonitor
override fun call(): LoopState {
    if (!sdkVersionMatch()) {
      return LoopState.Terminate
    }

    if (mHasDumped) {
      return LoopState.Terminate
    }

    return trackOOM()  //代码9
  }

代码9处trackOOM

private fun trackOOM(): LoopState {
    SystemInfo.refresh()   //代码10 收集内存信息

    mTrackReasons.clear()   //集合clear
    for (oomTracker in mOOMTrackers) {   //代码11 遍历trackers
      if (oomTracker.track()) {   //调用tracker
        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()   //内存dump
        }
      }

      return LoopState.Terminate
    }

    return LoopState.Continue
  }

代码10处手机一些系统的信息(java堆栈的最大内存,总内存,空闲的内存,使用的内存,内存使用率;虚拟地址空间大小,应用程序正在使用的物理内存的大小,共享使用该信号描述符的任务的个数,所有RAM大小,空闲物理内存,可用物理内存,内存的使用率)
代码11处,编译mOOMTrackers,调用集合的trace方法

 private val mOOMTrackers = mutableListOf(
    HeapOOMTracker(), ThreadOOMTracker(), FdOOMTracker(),
    PhysicalMemoryOOMTracker(), FastHugeMemoryOOMTracker()
  )

可以看到OOM集合,我们逐一分析

4.HeapOOMTracker

分析下HeapOOMTracker trace

override fun track(): Boolean {   //堆oom判断
    val heapRatio = SystemInfo.javaHeap.rate   // 代码12 javaHeap 使用率

    if (heapRatio > monitorConfig.heapThreshold
        && heapRatio >= mLastHeapRatio - HEAP_RATIO_THRESHOLD_GAP) {   //代码13大于阀值并且是上涨的趋势

      mOverThresholdCount++   //计数器++

      MonitorLog.i(TAG,
          "[meet condition] "
              + "overThresholdCount: $mOverThresholdCount"
              + ", heapRatio: $heapRatio"
              + ", usedMem: ${SizeUnit.BYTE.toMB(SystemInfo.javaHeap.used)}mb"
              + ", max: ${SizeUnit.BYTE.toMB(SystemInfo.javaHeap.max)}mb")
    } else {
      reset()
    }

    mLastHeapRatio = heapRatio

    return mOverThresholdCount >= monitorConfig.maxOverThresholdCount   //次数大于阀值 返回true
  }

代码12,获取java堆内存的使用率,是在上述代码 SystemInfo.refresh() 获取的堆栈数据,
代码13,判断内存的使用率是否超过阀值,大于阀值(08%/85%/90%根据机型的内存),并且是上涨趋势的,并且次数超过三次,就会返回true,从而执行后边的dump流程

5.ThreadOOMTracker

ThreadOOMTracker
override fun track(): Boolean {
    val threadCount = getThreadCount()   //代码14处get 线程数量

    if (threadCount > monitorConfig.threadThreshold
        && threadCount >= mLastThreadCount - THREAD_COUNT_THRESHOLD_GAP) {   //代码15处判断线程数量是否达到阀值,and 比上次多50
      mOverThresholdCount++

      MonitorLog.i(TAG,
          "[meet condition] "
              + "overThresholdCount:$mOverThresholdCount"
              + ", threadCount: $threadCount")

      dumpThreadIfNeed()
    } else {
      reset()
    }

    mLastThreadCount = threadCount

    return mOverThresholdCount >= monitorConfig.maxOverThresholdCount
  }

代码14处获取线程数量,代码15处线程数量与阀值(根据机型450/750)进行对比,并且比上次多50,返回true

6.FdOOMTracker

override fun track(): Boolean {
    val fdCount = getFdCount()   //代码16获取文件描述符
    if (fdCount > monitorConfig.fdThreshold && fdCount >= mLastFdCount - FD_COUNT_THRESHOLD_GAP) {
      mOverThresholdCount++

      MonitorLog.i(TAG,
          "[meet condition] "
              + "overThresholdCount: $mOverThresholdCount"
              + ", fdCount: $fdCount")

      dumpFdIfNeed()
    } else {
      reset()
    }

    mLastFdCount = fdCount

    return mOverThresholdCount >= monitorConfig.maxOverThresholdCount
  }

代码16处获取文件描述符的数量,然后与阀值进行比较(一般会设置为300),比上次多50,次数超过了阀值,则返回true

7 .PhysicalMemoryOOMTracker

物理内存的监控,目前还没有使用

8.FastHugeMemoryOOMTracker

override fun track(): Boolean {
    val javaHeap = SystemInfo.javaHeap

    // 高危阈值直接触发dump分析
    if (javaHeap.rate > monitorConfig.forceDumpJavaHeapMaxThreshold) {	//代码17,检测java堆内存的使用率
      mDumpReason = REASON_HIGH_WATERMARK
      MonitorLog.i(TAG, "[meet condition] fast huge memory allocated detected, " +
          "high memory watermark, force dump analysis!")
      return true
    }

    // 高差值直接dump
    val lastJavaHeap = SystemInfo.lastJavaHeap
    if (lastJavaHeap.max != 0L && javaHeap.used - lastJavaHeap.used
        > SizeUnit.KB.toByte(monitorConfig.forceDumpJavaHeapDeltaThreshold)) {
      mDumpReason = REASON_HUGE_DELTA
      MonitorLog.i(TAG, "[meet condition] fast huge memory allocated detected, " +
          "over the delta threshold!")
      return true
    }

    return false
  }

代码17 ,判断java堆内存使用是否查超过了阀值(默认0.9),或者比上次使用内存增长超过了阀值(默认350m),返回true

6.内存dump hprof

上述代码11处遍历OOMTracker对象,如果有一个trace返回true,dumpAndAnalysis

OOMMonitor
private fun trackOOM(): LoopState {
    SystemInfo.refresh()   //收集内存信息

    mTrackReasons.clear()   //集合clear
    for (oomTracker in mOOMTrackers) {   //遍历trackers
      if (oomTracker.track()) {   //调用tracker
        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()   //代码18内存dump
        }
      }

      return LoopState.Terminate
    }

    return LoopState.Continue
  }

代码18处,进行dump流程

OOMMonitor
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()

      val jsonFile = OOMFileManager.createJsonAnalysisFile(date)	//代码19
      val hprofFile = OOMFileManager.createHprofAnalysisFile(date).apply {	//代码20
        createNewFile()
        setWritable(true)
        setReadable(true)
      }

      MonitorLog.i(TAG, "hprof analysis dir:$hprofAnalysisDir")

      ForkJvmHeapDumper.getInstance().run {		//代码21
        dump(hprofFile.absolutePath)   //dump
      }

      MonitorLog.i(TAG, "end hprof dump", true)
      Thread.sleep(1000) // make sure file synced to disk.
      MonitorLog.i(TAG, "start hprof analysis")

      startAnalysisService(hprofFile, jsonFile, mTrackReasons.joinToString())   //代码22开始分析
    }.onFailure {
      it.printStackTrace()

      MonitorLog.i(TAG, "onJvmThreshold Exception " + it.message, true)
    }
  }

代码19 20 分别创建内存解析的json文件,hprof文件,
代码21,fork进行进行dump内存快照
代码22,分析内存快照
下边我们就来分析下如何dump内存快照以及内存快照的分析

ForkJvmHeapDumper
@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();  //代码23
      if (pid == 0) {
        // Child process
        Debug.dumpHprofData(path);   //代码24
        exitProcess();
      } else if (pid > 0) {
        // Parent process
        dumpRes = resumeAndWait(pid);   //代码25
        MonitorLog.i(TAG, "dump " + dumpRes + ", notify from pid " + pid);
      }
    } catch (IOException e) {
      MonitorLog.e(TAG, "dump failed caused by " + e);
      e.printStackTrace();
    }
    return dumpRes;
  }

代码23 suspend当前进行的所有线程,然后执行fork操作
代码24调用系统提供的api debug.dumpHprofData dump内存快照
代码25是执行在主进程的代码,恢复主进程suspend的线程
这里有两个问题说明下:
1.为什么要suspend所有的进程?
答:多线程执行的情况下,调用fork函数,仅会将发起调用的线程复制到子进程中,其他线程均在子进程中立即停止并消失,但是父进程全局变量的状态以及所有的pthreads对象(如互斥量,条件变量等)都会在子进程得以保留。因此,子进程dump hprof的时候,SuspendAll触发暂停是永远得不到其他线程的返回结果,导致子进程阻塞卡死。
解决版本就是先在主进程执行suspendAll,ThreadList中保存的所有线程状态为suspend,之后fork,子进程dump hprof,由于子进程共享父进程的ThreadList全局变量,因此认为所有的线程已经处于suspend状态,避免了子进程dump时触发暂停等不到线程返回结果的情况,fork完毕后立刻执行ResumeAll恢复运行。

2.如何fork进程?
答:在android中,suspend相关代码被编译成libart.so(Art),使用dlsym动态加载Suspend方法

pid_t HprofDump::SuspendAndFork() {
  KCHECKI(init_done_)

  if (android_api_ < __ANDROID_API_R__) {   //代码26
    suspend_vm_fnc_();
  } else if (android_api_ <= __ANDROID_API_S__) {
    void *self = __get_tls()[TLS_SLOT_ART_THREAD_SELF];
    sgc_constructor_fnc_((void *)sgc_instance_.get(), self, kGcCauseHprof,
                         kCollectorTypeHprof);
    ssa_constructor_fnc_((void *)ssa_instance_.get(), LOG_TAG, true);
    // avoid deadlock with child process
    exclusive_unlock_fnc_(*mutator_lock_ptr_, self);
    sgc_destructor_fnc_((void *)sgc_instance_.get());
  }

  pid_t pid = fork();   //代码27 fork一个进程
  if (pid == 0) {
    // Set timeout for child process
    alarm(60);   //SIGALRM
    prctl(PR_SET_NAME, "forked-dump-process");
  }
  return pid;
}

代码26 调用libart.so方法,suspend所有的线程
代码27 fork进程

7.内存hprof分析

上述代码22,会开启service分析hprof,

HeapAnalysisService
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 {   //代码28
      buildIndex(hprofFile)
    }.onFailure {
      it.printStackTrace()
      MonitorLog.e(OOM_ANALYSIS_EXCEPTION_TAG, "build index exception " + it.message, true)
      resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)
      return
    }

    buildJson(intent)		//代码29

    kotlin.runCatching {
      filterLeakingObjects()	//代码30
    }.onFailure {
      MonitorLog.i(OOM_ANALYSIS_EXCEPTION_TAG, "find leak objects exception " + it.message, true)
      resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)
      return
    }

    kotlin.runCatching {
      findPathsToGcRoot()		//代码31
    }.onFailure {
      it.printStackTrace()
      MonitorLog.i(OOM_ANALYSIS_EXCEPTION_TAG, "find gc path exception " + it.message, true)
      resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)
      return
    }

    fillJsonFile(jsonFile)

    resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK, null)

    System.exit(0);
  }

代码29 收集一些系统信息,存储到HeapReport.RunningInfo对象中,比如虚拟机堆内存信息,物理内存信息,虚拟内存信息,线程信息,fd信息
代码30,遍历镜像,找到泄漏的对象
以下代码分析流程很简单,大概总结下:
1.判断hprof存在的对象,如果是Activity,Activity对象的mDestoryed,mFinished都为true,则Activity泄漏
2.如果是Fragment,mFragmentManager为null,mCalled为true,泄漏
3.如果是Bitmap,mWidth,mHeight计算bitmap size大于阀值,大对象泄漏
4.基本类型数据,大于256m,大对象泄漏
5.对象数组,大于256m,大对象泄漏

private fun filterLeakingObjects() {
    val startTime = System.currentTimeMillis()
    MonitorLog.i(TAG, "filterLeakingObjects " + Thread.currentThread())

    val activityHeapClass = mHeapGraph.findClassByName(ACTIVITY_CLASS_NAME)   //activity
    val fragmentHeapClass = mHeapGraph.findClassByName(ANDROIDX_FRAGMENT_CLASS_NAME)   //fragment
        ?: mHeapGraph.findClassByName(NATIVE_FRAGMENT_CLASS_NAME)
        ?: mHeapGraph.findClassByName(SUPPORT_FRAGMENT_CLASS_NAME)
    val bitmapHeapClass = mHeapGraph.findClassByName(BITMAP_CLASS_NAME)  //bitmap
    val nativeAllocationHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLASS_NAME)   //
    val nativeAllocationThunkHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME)   //NativeAllocationRegistry
    val windowClass = mHeapGraph.findClassByName(WINDOW_CLASS_NAME)   //android.view.Window

    //缓存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) {   //instance 是 Activity
        val destroyField = instance[ACTIVITY_CLASS_NAME, DESTROYED_FIELD_NAME]!!   //get Activity mDestoryed
        val finishedField = instance[ACTIVITY_CLASS_NAME, FINISHED_FIELD_NAME]!!   //get Activity mFinished
        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) {   //判断是否是Fragment
        val fragmentManager = instance[fragmentHeapClass.name, FRAGMENT_MANAGER_FIELD_NAME]   //get Fragment mFragmentManager
        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) {   //bitmap对象
        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)
  }

使用案例

LeakMaker 这里模拟了几个泄漏,Activity泄漏,Bitmap大对象,Fragment泄漏,基本数据类型数组以及对象数组的大对象,我们执行下,然后分析下生成的Json文件
测试demo会创建大量的线程,触发dump流程
Json字段分析:
classInfos:则是在HeapAnalysisService.filterLeakingObjects遍历镜像的所有instance,收集的信息,每个对象对应的实例个数,这里可以看到Bitmap,LeakedActivity都存在实例
gcPaths:获取泄漏对象的路径,定位问题
leakObjects:大对象泄漏信息,存储Bitmap,基本类型数组,对象数据的泄漏的大小,className等信息
runningInfo:通过Linux文件或者系统Api获取到一些信息,虚拟机堆内存信息,物理内存信息,虚拟内存信息,线程信息,fd信息等,可以获取到当前系统的负载情况

LeakMaker
public static void makeLeak(Context context) {
    leakMakerList.add(new ActivityLeakMaker());
    leakMakerList.add(new BitmapLeakMaker());
    leakMakerList.add(new ByteArrayLeakMaker());
    leakMakerList.add(new FragmentLeakMaker());
    leakMakerList.add(new StringLeakMaker());
    for (LeakMaker leakMaker : leakMakerList) {
      leakMaker.startLeak(context);
    }

    for (int i = 0; i < 700; i++) {
      new Thread(() -> {
        try {
          Thread.sleep(200000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }).start();
    }
  }
{
    "classInfos":[
        {
            "className":"libcore.util.NativeAllocationRegistry$CleanerThunk",
            "instanceCount":"2386"
        },
        {
            "className":"libcore.util.NativeAllocationRegistry",
            "instanceCount":"32"
        },
        {
            "className":"com.android.internal.policy.HwPhoneWindow",
            "instanceCount":"3"
        },
        {
            "className":"android.graphics.Bitmap",
            "instanceCount":"1"
        },
        {
            "className":"com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
            "instanceCount":"1"
        }
    ],
    "gcPaths":[
        {
            "gcRoot":"System class",
            "instanceCount":1,
            "leakReason":"Primitive Array Size Over Threshold, 431037",
            "path":[
                {
                    "declaredClass":"android.icu.impl.coll.CollationRoot",
                    "reference":"android.icu.impl.coll.CollationRoot.rootSingleton",
                    "referenceType":"STATIC_FIELD"
                },
                {
                    "declaredClass":"android.icu.impl.coll.CollationTailoring",
                    "reference":"android.icu.impl.coll.CollationTailoring.trie",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"android.icu.impl.Trie2",
                    "reference":"android.icu.impl.Trie2_32.data32",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "reference":"int[]",
                    "referenceType":"array"
                }
            ],
            "signature":"72c7b40fff431b6442d8de34a1f395379db13b9"
        },
        {
            "gcRoot":"System class",
            "instanceCount":1,
            "leakReason":"Activity Leak",
            "path":[
                {
                    "declaredClass":"android.app.ActivityThread",
                    "reference":"android.app.ActivityThread.sCurrentActivityThread",
                    "referenceType":"STATIC_FIELD"
                },
                {
                    "declaredClass":"android.app.ActivityThread",
                    "reference":"android.app.ActivityThread.mActivities",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"android.util.ArrayMap",
                    "reference":"android.util.ArrayMap.mArray",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "declaredClass":"android.app.ActivityThread$ActivityClientRecord",
                    "reference":"android.app.ActivityThread$ActivityClientRecord.activity",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "reference":"com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
                    "referenceType":"instance"
                }
            ],
            "signature":"e0ceb5289d667a9c86a93c57856e90377d4e4"
        },
        {
            "gcRoot":"Local variable in native code",
            "instanceCount":1,
            "leakReason":"Bitmap Size Over Threshold, 1920x1080",
            "path":[
                {
                    "declaredClass":"java.lang.ClassLoader",
                    "reference":"dalvik.system.PathClassLoader.runtimeInternalObjects",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
                    "reference":"com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
                    "referenceType":"STATIC_FIELD"
                },
                {
                    "declaredClass":"java.util.ArrayList",
                    "reference":"java.util.ArrayList.elementData",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
                    "reference":"com.kwai.koom.demo.javaleak.test.BitmapLeakMaker.uselessObjectList",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.util.ArrayList",
                    "reference":"java.util.ArrayList.elementData",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "reference":"android.graphics.Bitmap",
                    "referenceType":"instance"
                }
            ],
            "signature":"38ba5ba71b7599737372f965417abcf2765dbb2a"
        },
        {
            "gcRoot":"Local variable in native code",
            "instanceCount":1,
            "leakReason":"Primitive Array Size Over Threshold, 524301",
            "path":[
                {
                    "declaredClass":"java.lang.ClassLoader",
                    "reference":"dalvik.system.PathClassLoader.runtimeInternalObjects",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
                    "reference":"com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
                    "referenceType":"STATIC_FIELD"
                },
                {
                    "declaredClass":"java.util.ArrayList",
                    "reference":"java.util.ArrayList.elementData",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
                    "reference":"com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker.uselessObjectList",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.util.ArrayList",
                    "reference":"java.util.ArrayList.elementData",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "declaredClass":"java.lang.Object[]",
                    "reference":"java.lang.Object[]",
                    "referenceType":"ARRAY_ENTRY"
                },
                {
                    "declaredClass":"com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker$ByteArrayHolder",
                    "reference":"com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker$ByteArrayHolder.array",
                    "referenceType":"INSTANCE_FIELD"
                },
                {
                    "reference":"byte[]",
                    "referenceType":"array"
                }
            ],
            "signature":"19eadfb7e6e6db237f5ab5540f1dfb9cc53c6"
        }
    ],
    "leakObjects":[
        {
            "className":"android.graphics.Bitmap",
            "extDetail":"1920 x 1080",
            "objectId":"318259112",
            "size":"2073600"
        },
        {
            "className":"int[]",
            "objectId":"2008600576",
            "size":"431037"
        },
        {
            "className":"byte[]",
            "objectId":"2009292800",
            "size":"524301"
        },
        {
            "className":"byte[]",
            "objectId":"2009821184",
            "size":"524301"
        },
        {
            "className":"char[]",
            "objectId":"2010349568",
            "size":"1048589"
        },
        {
            "className":"char[]",
            "objectId":"2011402256",
            "size":"1048589"
        }
    ],
    "runningInfo":{
        "buildModel":"JNY-AL10",
        "currentPage":"javaleak.JavaLeakTestActivity",
        "deviceMemAvaliable":"4127.039",
        "deviceMemTotal":"7703.6406",
        "dumpReason":"reason_thread_oom",
        "fdCount":"120",
        "filterInstanceTime":"1.014",
        "findGCPathTime":"7.273",
        "jvmMax":"384.0",
        "jvmUsed":"5.931076",
        "manufacture":"HUAWEI",
        "nowTime":"2023-07-16_17-12-57_575",
        "pss":"81.899414mb",
        "rss":"187.40625mb",
        "sdkInt":"29",
        "threadCount":"228",
        "threadList":[
            ".kwai.koom.demo,Jit thread pool,Signal Catcher,ADB-JDWP Connec,HeapTaskDaemon,ReferenceQueueD,FinalizerDaemon,FinalizerWatchd,Binder:21550_1,Binder:21550_2,Binder:21550_3,AppEyeUiProbeTh,Profile Saver,Binder:21550_4,RenderThread,mali-mem-purge,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-cmar-backe,mali-hist-dump,JDWP Transport ,JDWP Event Help,JDWP Command Re,queued-work-loo,LoopThread,Thread-4,Thread-5,Thread-6,Thread-7,Thread-8,Thread-9,Thread-10,Thread-11,Thread-12,Thread-13,Thread-14,Thread-15,Thread-16,Thread-17,Thread-18,Thread-19,Thread-20,Thread-21,Thread-22,Thread-23,Thread-24,Thread-25,Thread-26,Thread-27,Thread-28,Thread-29,Thread-30,Thread-31,Thread-32,Thread-33,Thread-34,Thread-35,Thread-36,Thread-37,Thread-38,Thread-39,Thread-40,Thread-41,Thread-42,Thread-43,Thread-44,Thread-45,Thread-46,Thread-47,Thread-48,Thread-49,Thread-50,Thread-51,Thread-52,Thread-53,Thread-54,Thread-55,Thread-56,Thread-57,Thread-58,Thread-59,Thread-60,Thread-61,Thread-62,Thread-63,Thread-64,Thread-65,Thread-66,Thread-67,Thread-68,Thread-69,Thread-70,Thread-71,Thread-72,Thread-73,Thread-74,Thread-75,Thread-76,Thread-77,Thread-78,Thread-79,Thread-80,Thread-81,Thread-82,Thread-83,Thread-84,Thread-85,Thread-86,Thread-87,Thread-88,Thread-89,Thread-90,Thread-91,Thread-92,Thread-93,Thread-94,Thread-95,Thread-96,Thread-97,Thread-98,Thread-99,Thread-100,Thread-101,Thread-102,Thread-103,Thread-104,Thread-105,Thread-106,Thread-107,Thread-108,Thread-109,Thread-110,Thread-111,Thread-112,Thread-113,Thread-114,Thread-115,Thread-116,Thread-117,Thread-118,Thread-119,Thread-120,Thread-121,Thread-122,Thread-123,Thread-124,Thread-125,Thread-126,Thread-127,Thread-128,Thread-129,Thread-130,Thread-131,Thread-132,Thread-133,Thread-134,Thread-135,Thread-136,Thread-137,Thread-138,Thread-139,Thread-140,Thread-141,Thread-142,Thread-143,Thread-144,Thread-145,Thread-146,Thread-147,Thread-148,Thread-149,Thread-150,Thread-151,Thread-152,Thread-153,Thread-154,Thread-155,Thread-156,Thread-157,Thread-158,Thread-159,Thread-160,Thread-161,Thread-162,Thread-163,Thread-164,Thread-165,Thread-166,Thread-167,Thread-168,Thread-169,Thread-170,Thread-171,Thread-172,Thread-173,Thread-174,Thread-175,Thread-176,Thread-177,Thread-178,Thread-179,Thread-180,Thread-181,Thread-182,Thread-183,Thread-184,Thread-185,Thread-186,Thread-187,Thread-188,Thread-189,Thread-190,Thread-191,Thread-192,Thread-193,Thread-194,Thread-195,Thread-196,Thread-197,Thread-198,Thread-199,Thread-200,Thread-201,Thread-202,Thread-203,Thread-204,Thread-205,Thread-206,Thread-207,Thread-208,Thread-209,Thread-210,Thread-211,Thread-212,Thread-213,Thread-214,Thread-215,Thread-216,Thread-217,Thread-218,Thread-219,Thread-220,Thread-221,Thread-222,Thread-223,Thread-224,Thread-225,Thread-226,Thread-227,Thread-228,Thread-229,Thread-230,Thread-231,Thread-232,Thread-233,Thread-234,Thread-235,Thread-236,Thread-237,Thread-238,Thread-239,Thread-240,Thread-241,Thread-242,Thread-243,Thread-244,Thread-245,Thread-246,Thread-247,Thread-248,Thread-249,Thread-250,Thread-251,Thread-252,Thread-253"
        ],
        "usageSeconds":"6",
        "vss":"9174.836mb"
    }
}

到这Koom Java内存的监控分析完了,下一篇会Koom Native内存监控,以及线程泄漏监控

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值