koom koom-java-leak 源码分析

本文详细分析了Koom库中针对Java内存泄漏的检测和处理机制,包括AnalysisExtraData的分析信息,HeapReport的上报,OOMReportUploader的问题上传,以及不同类型的堆dump方法。同时介绍了内存和文件管理,系统信息获取,以及OOMMonitor如何根据多种跟踪器判断并触发内存dump分析。

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

1.AnalysisExtraData.kt 包含原因,分析时间,当前页面信息

class AnalysisExtraData {
  var reason: String? = null        //reason原因

  var usageSeconds: String? = null  //时间

  var currentPage: String? = null   //当前页面
}

2.HeapReport上报的信息

public class HeapReport {

    public RunningInfo runningInfo = new RunningInfo();
    public List<GCPath> gcPaths = new ArrayList<>();//引用链gc path of suspected objects
    public List<ClassInfo> classInfos = new ArrayList<>();//类及其实例数量Class's instances count list
    public List<LeakObject> leakObjects = new ArrayList<>();//泄漏的对象
    public Boolean analysisDone;//flag to record whether hprof is analyzed already.
    public Integer reAnalysisTimes;//flag to record hprof reanalysis times.

    //device and app running info
    public static class RunningInfo {
        //JVM info
        public String jvmMax;//jvm max memory in MB jvm最大内存
        public String jvmUsed;//jvm used memory in MB jvm已经用过多少了

        //https://my.oschina.net/u/4592355/blog/5004330
        //https://www.cnblogs.com/liyuanhong/articles/7839762.html
        //在linux下表示内存的耗用情况有四种不同的表现形式:
        // VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
        // RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
        // PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
        // USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
        //memory info
        public String vss;//vss memory in MB 进程vss
        public String pss;//pss memory in MB 进程pss
        public String rss;//rss memory in MB 进程rss
        public String threadCount;//线程个数
        public String fdCount;    //fd个数
        public List<String> threadList = new ArrayList<>();//线程信息
        public List<String> fdList = new ArrayList<>();    //fd信息

        //Device info
        public String sdkInt;       //sdk版本
        public String manufacture;  //厂商
        public String buildModel;   //版本

        //App info
        public String appVersion;
        public String currentPage;  //页面
        public String usageSeconds; //耗费时间
        public String nowTime;      //上报时间
        public String deviceMemTotal;     //device内存
        public String deviceMemAvaliable; //device可用内存

        public String dumpReason;//heap dump trigger reason,dump原因
        public String analysisReason;//analysis trigger reason,分析原因

        //KOOM Perf data
        public String koomVersion;
        public String filterInstanceTime; //过滤泄漏对象所花的时间
        public String findGCPathTime;     //发现GC引用链时间
    }

    /**
     * GC Path means path of object to GC Root, it can also be called as reference chain.
     */
    public static class GCPath {
        public Integer instanceCount;//todo instances number of same path to gc root
        public String leakReason;//reason of why instance is suspected
        public String gcRoot;
        public String signature;//signature are computed by the sha1 of reference chain
        public List<PathItem> path = new ArrayList<>();

        //引用链Item
        public static class PathItem {
            String reference;//referenced instance's classname + filed name
            String referenceType;//todo such as INSTANCE_FIELD/ARRAY_ENTRY/STATIC_FIELD
            String declaredClass;//对于从祖先类继承字段的情况。for cases when filed is inherited from ancestor's class.
        }
    }

    /**
     * ClassInfo contains data which describes the instances number of the Class.
     * ClassInfo 包含描述类的实例数量的数据。
     */
    public static class ClassInfo {
        public String className;
        public String instanceCount;//类的实例数量All instances's count of this class.
        public String leakInstanceCount;//All leaked instances's count of this class.
    }

    public static class LeakObject {
        public String className;
        public String size;
        public String objectId;
        public String extDetail;//todo
    }
}

3.OOMReportUploader.kt 上传问题

interface OOMReportUploader {
  /**
   * 注意:外部调用完upload后,切记自行删除
   */
  fun upload(file: File, content: String)
}

4.OOMHprofUploader 上传hprof

interface OOMHprofUploader {

  enum class HprofType {
    ORIGIN, STRIPPED
  }

  /**
   * 注意:外部调用完upload后,切记自行删除
   */
  fun upload(file: File, type: HprofType)
}

5.OOMPreferenceManager 保存分析次数,第一次启动时间

internal object OOMPreferenceManager {
    private const val PREFERENCE_NAME = "koom_hprof_analysis"

    private val mPreferences by lazy { mSharedPreferencesInvoker(PREFERENCE_NAME) }

    private lateinit var mSharedPreferencesInvoker: (String) -> SharedPreferences
    private lateinit var mPrefix: String

    fun init(sharedPreferencesInvoker: (String) -> SharedPreferences) {
        mSharedPreferencesInvoker = sharedPreferencesInvoker
        mPrefix = "${MonitorBuildConfig.VERSION_NAME}_"
    }

  //分析次数
    fun getAnalysisTimes(): Int {
        return mPreferences.getInt("${mPrefix}times", 0)
    }
  
    fun increaseAnalysisTimes() {
        mPreferences.edit()
                .also { clearUnusedPreference(mPreferences, it) }
                .putInt("${mPrefix}times", mPreferences.getInt("${mPrefix}times", 0) + 1)
                .apply()
    }

  //第一次启动时间
    fun getFirstLaunchTime(): Long {
        var time = mPreferences.getLong("${mPrefix}first_analysis_time", 0)
        if (time == 0L) {
            time = System.currentTimeMillis()
            setFirstLaunchTime(time)
        }
        return time
    }

    fun setFirstLaunchTime(time: Long) {
        if (mPreferences.contains("${mPrefix}first_analysis_time")) {
            return
        }

        return mPreferences.edit()
                .putLong("${mPrefix}first_analysis_time", time)
                .apply()
    }

  //清除之前版本的
    private fun clearUnusedPreference(//清除之前版本的
            preferences: SharedPreferences,
            editor: SharedPreferences.Editor
    ) {
        for (key in preferences.allKeys) {
            if (!key.startsWith(mPrefix)) {
                editor.remove(key)
            }
        }
    }
}

6.OOMFileManager 文件相关的

internal object OOMFileManager {
    private const val TIME_FORMAT = "yyyy-MM-dd_HH-mm-ss_SSS"

    private lateinit var mRootDirInvoker: (String) -> File//mRootDirInvoker表达式
    private lateinit var mPrefix: String    //前缀是个version

    private lateinit var mRootPath: String  //root路径

    val rootDir by lazy {
        if (this::mRootDirInvoker.isInitialized)//已初始化
            mRootDirInvoker("oom")
        else
            File(mRootPath)
    }

    @JvmStatic
    val hprofAnalysisDir by lazy { File(rootDir, "memory/hprof-aly").apply { mkdirs() } }

    @JvmStatic
    val manualDumpDir by lazy { File(rootDir, "memory/hprof-man").apply { mkdirs() } }

    @JvmStatic
    val threadDumpDir by lazy { File(hprofAnalysisDir, "thread").apply { mkdirs() } }

    @JvmStatic
    val fdDumpDir by lazy { File(hprofAnalysisDir, "fd").apply { mkdirs() } }

    @JvmStatic
    fun init(rootDirInvoker: (String) -> File) {//有两个init,如果有mRootDirInvoker,则以mRootDirInvoker为主
        mRootDirInvoker = rootDirInvoker
        mPrefix = "${MonitorBuildConfig.VERSION_NAME}_"
    }

    @JvmStatic
    fun init(rootPath: String?) {//有两个init,如果没有有mRootDirInvoker,以mRootPath为主
        if (rootPath != null) {
            mRootPath = rootPath
        }
        mPrefix = "${MonitorBuildConfig.VERSION_NAME}_"
    }

    @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()
        }
    }

    @JvmStatic
    fun createHprofOOMDumpFile(date: Date): File {
        val time = SimpleDateFormat(TIME_FORMAT, Locale.CHINESE).format(date)
        return File(manualDumpDir, "$mPrefix$time.hprof").also {
            manualDumpDir.mkdirs()
        }
    }

    @JvmStatic
    fun createDumpFile(dumpDir: File): File {
        return File(dumpDir, "dump.txt").also {
            dumpDir.mkdirs()
        }
    }

    @JvmStatic
    fun isSpaceEnough(): Boolean {
        //https://blog.youkuaiyun.com/suyimin2010/article/details/86680731
        //getPath() 方法跟创建 File 对象时传入的路径参数有关,返回构造时传入的路径
        //getAbsolutePath() 方法返回文件的绝对路径,如果构造的时候是全路径就直接返回全路径,如果构造时是相对路径,就返回当前目录的路径 + 构造 File 对象时的路径
        //getCanonicalPath() 方法返回绝对路径,会把 ..\ 、.\ 这样的符号解析掉
        val statFs = StatFs(hprofAnalysisDir.canonicalPath)
        val blockSize = statFs.blockSizeLong//文件系统上块的大小(以字节为单位)
        val availableBlocks = statFs.availableBlocks.toLong()

        return blockSize * availableBlocks > 1.2 * 1024 * 1024//todo 1.2G ? 1.2M
    }
}

7.OOMHeapDumper dump有四种,simple + fork + strip + forkstrip

//dump有四种,simple+fork+strip+forkstrip
object OOMHeapDumper {
  private const val TAG = "OOMHeapDumper"

  private fun dump(dumper: HeapDumper) {
    try {
      MonitorLog.i(TAG, "dump hprof start")

      val hprofFile = OOMFileManager.createHprofOOMDumpFile(Date())

      val start = System.currentTimeMillis()

      hprofFile.createNewFile()
      dumper.dump(hprofFile.absolutePath)

      val end = System.currentTimeMillis()

      MonitorLog.i(TAG, "dump hprof complete," +
          " dumpTime:" + (end - start) +
          " fileName:" + hprofFile.name +
          " origin fileSize:" + SizeUnit.BYTE.toMB(hprofFile.length()) +
          " JVM max memory:" + SizeUnit.BYTE.toMB(Runtime.getRuntime().maxMemory()) +
          " JVM  free memory:" + SizeUnit.BYTE.toMB(Runtime.getRuntime().freeMemory()) +
          " JVM total memory:" + SizeUnit.BYTE.toMB(Runtime.getRuntime().totalMemory()), true)
    } catch (e: Throwable) {
      e.printStackTrace()

      MonitorLog.i(TAG, "dumpStripHprof failed: ${e.message}")
    }
  }

  @JvmStatic
  fun simpleDump() {
    MonitorLog.i(TAG, "simpleDump")
    dump(StandardHeapDumper())
  }

  @JvmStatic
  fun forkDump() {
    MonitorLog.i(TAG, "forkDump")
    dump(ForkJvmHeapDumper())
  }

  @JvmStatic
  fun stripDump() {
    MonitorLog.i(TAG, "dumpStripHprof")
    dump(StripHprofHeapDumper())
  }

  @JvmStatic
  fun forkDumpStrip() {
    MonitorLog.i(TAG, "forkDumpStrip")

    dump(ForkStripHeapDumper())
  }

}

8.OOMMonitorConfig配置

class OOMMonitorConfig(
        val analysisMaxTimesPerVersion: Int,  //每个版本最多分析次数
        val analysisPeriodPerVersion: Int,    //每个版本的前15天才分析,超过这个时间段不再dump

        val heapThreshold: Float,         //heap阈值
        val fdThreshold: Int,             //fd阈值
        val threadThreshold: Int,         //线程阈值
        val deviceMemoryThreshold: Float, //device memory阈值 还剩多少容量? todo
        val maxOverThresholdCount: Int,   //超过最大次数阈值 3次 todo
        val forceDumpJavaHeapMaxThreshold: Float,//强制dump java heap最大阈值 0.9大于等于heapThreshold todo
        val forceDumpJavaHeapDeltaThreshold: Int,//强制dump java heap delta阈值 Delta 三角洲 意思是增量 todo

        val loopInterval: Long, //轮询间隔

        val enableHprofDumpAnalysis: Boolean,//是否开启hprofdump分析

        val hprofUploader: OOMHprofUploader?,//hprof上传
        val reportUploader: OOMReportUploader?//日志上传
) : MonitorConfig<OOMMonitor>() {

    class Builder : MonitorConfig.Builder<OOMMonitorConfig> {
        companion object {
            private val DEFAULT_HEAP_THRESHOLD by lazy {
                val maxMem = SizeUnit.BYTE.toMB(Runtime.getRuntime().maxMemory())
                when {
                    maxMem >= 512 - 10 -> 0.8f
                    maxMem >= 256 - 10 -> 0.85f
                    else -> 0.9f
                }
            }

            private val DEFAULT_THREAD_THRESHOLD by lazy {
                if (MonitorBuildConfig.ROM == "EMUI" && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
                    450
                } else {
                    750
                }
            }
        }

        private var mAnalysisMaxTimesPerVersion = 5                         //每个版本最多分析5次
        private var mAnalysisPeriodPerVersion = 15 * 24 * 60 * 60 * 1000    //每个版本的前15天才分析,超过这个时间段不再dump

        private var mHeapThreshold: Float? = null
        private var mVssSizeThreshold = 3_650_000 //Only for 32 bit cpu devices. 360M todo 有用到吗
        private var mFdThreshold = 1000             //fd阈值1000
        private var mThreadThreshold: Int? = null   //线程阈值
        private var mDeviceMemoryThreshold: Float = 0.05f//todo
        private var mForceDumpJavaHeapMaxThreshold = 0.90f//达到javaheap的90%的时候强制dump todo
        private var mForceDumpJavaHeapDeltaThreshold = 350_000 //java heap rise 350m in a very short time.短时间增量350M的时候
        private var mMaxOverThresholdCount = 3//超过3次
        private var mLoopInterval = 15_000L //15s轮询一次

        private var mEnableHprofDumpAnalysis = true //enable hprof analysis

        private var mHprofUploader: OOMHprofUploader? = null
        private var mReportUploader: OOMReportUploader? = null

        fun setAnalysisMaxTimesPerVersion(analysisMaxTimesPerVersion: Int) = apply {
            mAnalysisMaxTimesPerVersion = analysisMaxTimesPerVersion
        }

        fun setAnalysisPeriodPerVersion(analysisPeriodPerVersion: Int) = apply {
            mAnalysisPeriodPerVersion = analysisPeriodPerVersion
        }

        /**
         * @param heapThreshold: 堆内存的使用比例[0.0, 1.0]
         */
        fun setHeapThreshold(heapThreshold: Float) = apply {
            mHeapThreshold = heapThreshold
        }

        /**
         * @param vssSizeThreshold: 单位是kb
         */
        fun setVssSizeThreshold(vssSizeThreshold: Int) = apply {
            mVssSizeThreshold = vssSizeThreshold
        }

        fun setFdThreshold(fdThreshold: Int) = apply {
            mFdThreshold = fdThreshold
        }

        fun setThreadThreshold(threadThreshold: Int) = apply {
            mThreadThreshold = threadThreshold
        }

        fun setMaxOverThresholdCount(maxOverThresholdCount: Int) = apply {
            mMaxOverThresholdCount = maxOverThresholdCount
        }

        fun setLoopInterval(loopInterval: Long) = apply {
            mLoopInterval = loopInterval
        }

        fun setEnableHprofDumpAnalysis(enableHprofDumpAnalysis: Boolean) = apply {
            mEnableHprofDumpAnalysis = enableHprofDumpAnalysis
        }

        fun setDeviceMemoryThreshold(deviceMemoryThreshold: Float) = apply {
            mDeviceMemoryThreshold = deviceMemoryThreshold
        }

        fun setForceDumpJavaHeapDeltaThreshold(forceDumpJavaHeapDeltaThreshold: Int) = apply {
            mForceDumpJavaHeapDeltaThreshold = forceDumpJavaHeapDeltaThreshold
        }

        fun setForceDumpJavaHeapMaxThreshold(forceDumpJavaHeapMaxThreshold: Float) = apply {
            mForceDumpJavaHeapMaxThreshold = forceDumpJavaHeapMaxThreshold
        }

        fun setHprofUploader(hprofUploader: OOMHprofUploader) = apply {
            mHprofUploader = hprofUploader
        }

        fun setReportUploader(reportUploader: OOMReportUploader) = apply {
            mReportUploader = reportUploader
        }

        override fun build() = OOMMonitorConfig(
                analysisMaxTimesPerVersion = mAnalysisMaxTimesPerVersion,
                analysisPeriodPerVersion = mAnalysisPeriodPerVersion,

                heapThreshold = mHeapThreshold ?: DEFAULT_HEAP_THRESHOLD,
                fdThreshold = mFdThreshold,
                threadThreshold = mThreadThreshold ?: DEFAULT_THREAD_THRESHOLD,
                deviceMemoryThreshold = mDeviceMemoryThreshold,
                maxOverThresholdCount = mMaxOverThresholdCount,
                loopInterval = mLoopInterval,
                enableHprofDumpAnalysis = mEnableHprofDumpAnalysis,

                forceDumpJavaHeapMaxThreshold = mForceDumpJavaHeapMaxThreshold,
                forceDumpJavaHeapDeltaThreshold = mForceDumpJavaHeapDeltaThreshold,

                hprofUploader = mHprofUploader,
                reportUploader = mReportUploader
        )
    }
}

9.SizeUnit.kt 转换BYTE,KB,MB

sealed class SizeUnit {
  abstract fun toByte(value: Long): Float
  abstract fun toKB(value: Long): Float
  abstract fun toMB(value: Long): Float

  abstract fun toByte(value: Int): Float
  abstract fun toKB(value: Int): Float
  abstract fun toMB(value: Int): Float

  object BYTE : SizeUnit() {
    override fun toByte(value: Long) = value.toFloat()

    override fun toKB(value: Long) = value / 1024.0f

    override fun toMB(value: Long) = value / 1024.0f / 1024.0f

    override fun toByte(value: Int) = value.toFloat()

    override fun toKB(value: Int) = value / 1024.0f

    override fun toMB(value: Int) = value / 1024.0f / 1024.0f
  }

  object KB : SizeUnit() {
    override fun toByte(value: Long) = value * 1024.0f

    override fun toKB(value: Long) = value.toFloat()

    override fun toMB(value: Long) = value / 1024.0f

    override fun toByte(value: Int) = value * 1024.0f

    override fun toKB(value: Int) = value.toFloat()

    override fun toMB(value: Int) = value / 1024.0f
  }

  object MB : SizeUnit() {
    override fun toByte(value: Long) = value * 1024.0f * 1024.0f

    override fun toKB(value: Long) = value * 1024.0f

    override fun toMB(value: Long) = value.toFloat()

    override fun toByte(value: Int) = value * 1024.0f * 1024.0f

    override fun toKB(value: Int) = value * 1024.0f

    override fun toMB(value: Int) = value.toFloat()
  }
}

10.SystemInfo.kt 获取当前的ProcStatus/MemInfo/JavaHeap

internal object SystemInfo {
    private const val TAG = "OOMMonitor_SystemInfo"
    //https://my.oschina.net/u/4592355/blog/5004330
    //VSS - Virtual Set Size (用处不大)虚拟耗用内存(包含共享库占用的全部内存,以及分配但未使用内存)。
    // 其大小还包括了可能不在RAM中的内存(比如虽然malloc分配了空间,但尚未写入)。
    // VSS 很少被用于判断一个进程的真实内存使用量。
    //RSS - Resident Set Size (用处不大)
    //实际使用物理内存(包含共享库占用的全部内存)。但是RSS还是可能会造成误导,因为它仅仅表示该进程所使用的所有共享库的大小,
    // 它不管有多少个进程使用该共享库,该共享库仅被加载到内存一次。所以RSS并不能准确反映单进程的内存占用情况。
    //PSS - Proportional Set Size (仅供参考)
    //实际使用的物理内存(比例分配共享库占用的内存,按照进程数等比例划分)。
    //例如:如果有三个进程都使用了一个共享库,共占用了30页内存。那么PSS将认为每个进程分别占用该共享库10页的大小。
    //PSS是非常有用的数据,因为系统中所有进程的PSS都相加的话,就刚好反映了系统中的 总共占用的内存。
    // 而当一个进程被销毁之后, 其占用的共享库那部分比例的PSS,将会再次按比例分配给余下使用该库的进程。
    //这样PSS可能会造成一点的误导,因为当一个进程被销毁后, PSS不能准确地表示返回给全局系统的内存。
    //USS - Unique Set Size (非常有用)
    //进程独自占用的物理内存(不包含共享库占用的内存)。USS是非常非常有用的数据,因为它反映了运行一个特定进程真实的边际成本
    // (增量成本)。当一个进程被销毁后,USS是真实返回给系统的内存。当进程中存在一个可疑的内存泄露时,USS是最佳观察数据。
    private val VSS_REGEX = "VmSize:\\s*(\\d+)\\s*kB".toRegex()
    private val RSS_REGEX = "VmRSS:\\s*(\\d+)\\s*kB".toRegex()
    private val THREADS_REGEX = "Threads:\\s*(\\d+)\\s*".toRegex()
    private val MEM_TOTAL_REGEX = "MemTotal:\\s*(\\d+)\\s*kB".toRegex()
    private val MEM_FREE_REGEX = "MemFree:\\s*(\\d+)\\s*kB".toRegex()
    private val MEM_AVA_REGEX = "MemAvailable:\\s*(\\d+)\\s*kB".toRegex()
    //https://www.jianshu.com/p/9edfe9d5eb34
    //ion disp:display 相关的ion模块内存占用 ion是离子 todo
    //cma usage:cma模块占用
    private val MEM_CMA_REGEX = "CmaTotal:\\s*(\\d+)\\s*kB".toRegex()
    private val MEM_ION_REGEX = "ION_heap:\\s*(\\d+)\\s*kB".toRegex()

    var procStatus = ProcStatus()
    var lastProcStatus = ProcStatus()

    var memInfo = MemInfo()
    var lastMemInfo = MemInfo()

    var javaHeap = JavaHeap()
    var lastJavaHeap = JavaHeap()

    //selinux权限问题,先注释掉
    //var dmaZoneInfo: ZoneInfo = ZoneInfo()
    //var normalZoneInfo: ZoneInfo = ZoneInfo()

    fun refresh() {
        lastJavaHeap = javaHeap
        lastMemInfo = memInfo
        lastProcStatus = procStatus

        javaHeap = JavaHeap()
        procStatus = ProcStatus()
        memInfo = MemInfo()

        javaHeap.max = Runtime.getRuntime().maxMemory()
        javaHeap.total = Runtime.getRuntime().totalMemory()
        javaHeap.free = Runtime.getRuntime().freeMemory()
        javaHeap.used = javaHeap.total - javaHeap.free
        javaHeap.rate = 1.0f * javaHeap.used / javaHeap.max

        //读取"/proc/self/status"文件获取procStatus
        File("/proc/self/status").forEachLineQuietly { line ->
            if (procStatus.vssInKb != 0 && procStatus.rssInKb != 0
                    && procStatus.thread != 0) return@forEachLineQuietly

            when {
                line.startsWith("VmSize") -> {
                    procStatus.vssInKb = VSS_REGEX.matchValue(line)
                }

                line.startsWith("VmRSS") -> {
                    procStatus.rssInKb = RSS_REGEX.matchValue(line)
                }

                line.startsWith("Threads") -> {
                    procStatus.thread = THREADS_REGEX.matchValue(line)
                }
            }
        }

        //读取"/proc/meminfo"文件获取memInfo
        File("/proc/meminfo").forEachLineQuietly { line ->
            when {
                line.startsWith("MemTotal") -> {
                    memInfo.totalInKb = MEM_TOTAL_REGEX.matchValue(line)
                }

                line.startsWith("MemFree") -> {
                    memInfo.freeInKb = MEM_FREE_REGEX.matchValue(line)
                }

                line.startsWith("MemAvailable") -> {
                    memInfo.availableInKb = MEM_AVA_REGEX.matchValue(line)
                }

                line.startsWith("CmaTotal") -> {
                    memInfo.cmaTotal = MEM_CMA_REGEX.matchValue(line)
                }

                line.startsWith("ION_heap") -> {
                    memInfo.IONHeap = MEM_ION_REGEX.matchValue(line)
                }
            }
        }

        memInfo.rate = 1.0f * memInfo.availableInKb / memInfo.totalInKb

        MonitorLog.i(TAG, "----OOM Monitor Memory----")
        MonitorLog.i(TAG, "[java] max:${javaHeap.max} used ratio:${(javaHeap.rate * 100).toInt()}%")
        MonitorLog.i(TAG, "[proc] VmSize:${procStatus.vssInKb}kB VmRss:${procStatus.rssInKb}kB " + "Threads:${procStatus.thread}")
        MonitorLog.i(TAG, "[meminfo] MemTotal:${memInfo.totalInKb}kB MemFree:${memInfo.freeInKb}kB " + "MemAvailable:${memInfo.availableInKb}kB")
        MonitorLog.i(TAG, "avaliable ratio:${(memInfo.rate * 100).toInt()}% CmaTotal:${memInfo.cmaTotal}kB ION_heap:${memInfo.IONHeap}kB")
    }

    data class ProcStatus(var thread: Int = 0, var vssInKb: Int = 0, var rssInKb: Int = 0)

    data class MemInfo(var totalInKb: Int = 0, var freeInKb: Int = 0, var availableInKb: Int = 0,
                       var IONHeap: Int = 0, var cmaTotal: Int = 0, var rate: Float = 0f)

    data class JavaHeap(var max: Long = 0, var total: Long = 0, var free: Long = 0,
                        var used: Long = 0, var rate: Float = 0f)

    //正则表达式
    private fun Regex.matchValue(s: String) = matchEntire(s.trim())
            ?.groupValues?.getOrNull(1)?.toInt() ?: 0

    //读取文件的每一行
    private fun File.forEachLineQuietly(charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit) {
        kotlin.runCatching {
            // Note: close is called at forEachLineQuietly
            BufferedReader(InputStreamReader(FileInputStream(this), charset)).forEachLine(action)
        }.onFailure { exception -> exception.printStackTrace() }
    }

    /**
     * 设备是否支持arm64
     */
    fun isSupportArm64(): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            return false
        }

        return supportedAbis().contains("arm64-v8a")
    }

    fun supportedAbis(): Array<String?> {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                && Build.SUPPORTED_ABIS.isNotEmpty()) {
            Build.SUPPORTED_ABIS
        } else if (!TextUtils.isEmpty(Build.CPU_ABI2)) {//todo
            arrayOf(Build.CPU_ABI, Build.CPU_ABI2)
        } else {
            arrayOf(Build.CPU_ABI)
        }
    }
}

11.StandardHeapDumper直接调用Debug.dumpHprofData

public class StandardHeapDumper extends HeapDumper {

  @Override
  public boolean dump(String path) {
    try {
      Debug.dumpHprofData(path);
    } catch (IOException e) {
      e.printStackTrace();
      return false;
    }
    return true;
  }
}

12.HeapDumper dump方法

public abstract class HeapDumper {

  protected final boolean soLoaded;

  public HeapDumper() {
    soLoaded = NativeHandler.load();
  }

  /**
   * dump may cost several seconds, make sure called in a separated thread.
   *
   * @param path dump file
   * @return dump result success or not
   */
  public abstract boolean dump(String path);
}

13.NativeHandler加载koom-java native库

public class NativeHandler {
  private static boolean sSoLoaded;

  public static native boolean isARM64();

  public static boolean load() {
    if (!sSoLoaded) {
      return sSoLoaded = loadSoQuietly("koom-java");
    }

    return true;
  }

  static {
    load();
  }
}

14.ForkStripHeapDumper 创建裁剪StripHprofHeapDumper,创建ForkJvmHeapDumper

public class ForkStripHeapDumper extends HeapDumper {
    private static final String TAG = "OOMMonitor_ForkStripHeapDumper";

    public ForkStripHeapDumper() {
        super();
    }

    @Override
    public boolean dump(String path) {
        MonitorLog.e(TAG, "dump " + path);
        boolean dumpRes;
        try {
            //创建裁剪StripHprofHeapDumper
            StripHprofHeapDumper stripHprofHeapDumper = new StripHprofHeapDumper();
            stripHprofHeapDumper.initStripDump();
            stripHprofHeapDumper.hprofName(path);
            //创建ForkJvmHeapDumper
            ForkJvmHeapDumper forkJvmHeapDumper = new ForkJvmHeapDumper();
            dumpRes = forkJvmHeapDumper.dump(path);
            MonitorLog.e(TAG, "dump end");
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return dumpRes;
    }
}

15.StripHprofHeapDumper分为4步,1.initStripDump 2.hprofName 3.Debug.dumpHprofData 4.isStripSuccess

public class StripHprofHeapDumper extends HeapDumper {

  private static final String TAG = "OOMMonitor_StripHprofHeapDumper";

  public StripHprofHeapDumper() {
    super();
    if (soLoaded) {
      //step 1 
      initStripDump();
    }
  }

  @Override
  public boolean dump(String path) {
    MonitorLog.i(TAG, "dump " + path);
    if (!soLoaded) {
      MonitorLog.e(TAG, "dump failed caused by so not loaded!");
      return false;
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
      MonitorLog.e(TAG, "dump failed caused by version net permitted!");
      return false;
    }

    boolean dumpRes = false;
    try {
      //step 2
      hprofName(path);
      //step 3
      Debug.dumpHprofData(path);
      //step 4
      dumpRes = isStripSuccess();
    } catch (IOException e) {
      e.printStackTrace();
    }

    return dumpRes;
  }

  //step 1
  public native void initStripDump();

  //step 2
  public native void hprofName(String name);

  //step 4
  public native boolean isStripSuccess();
}

16.ForkJvmHeapDumper fork子进程,dump

//https://blog.youkuaiyun.com/biaozige/article/details/81171637
//art就是虚拟机
//JAVA 代码是怎么执行的?
//为了使代码和平台无关,JAVA开发了 JVM,即 Java 虚拟机。它为每一个平台开发一个 JVM,也就意味着 JVM 是和平台相关的。
//Java 编译器将 .java 文件转换成 .class文件,也就是字节码。最终将字节码提供给 JVM,由 JVM 将它转换成机器码。
//
//这比解释器要快但是比 C++ 编译要慢。
//
//Android 代码是怎么执行的
//在 Android 中,Java 类被转换成 DEX 字节码。DEX 字节码通过 ART 或者 Dalvik runtime 转换成机器码。
//这里 DEX 字节码和设备架构无关。
//
//Dalvik 是一个基于 JIT(Just in time)编译的引擎。使用 Dalvik 存在一些缺点,所以从 Android 4.4(Kitkat)
// 开始引入了 ART 作为运行时,从 Android 5.0(Lollipop)开始 ART 就全面取代了Dalvik。Android 7.0 向 ART 
// 中添加了一个 just-in-time(JIT)编译器,这样就可以在应用运行时持续的提高其性能。
public class ForkJvmHeapDumper extends HeapDumper {

    private static final String TAG = "OOMMonitor_ForkJvmHeapDumper";

    public ForkJvmHeapDumper() {
        super();
        if (soLoaded) {
            init();
        }
    }

    @Override
    public boolean dump(String path) {
        MonitorLog.i(TAG, "dump " + path);
        if (!soLoaded) {
            MonitorLog.e(TAG, "dump failed caused by so not loaded!");
            return false;
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
                || Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
            MonitorLog.e(TAG, "dump failed caused by version not supported!");
            return false;
        }

        boolean dumpRes = false;
        try {
            MonitorLog.i(TAG, "before suspend and fork.");
            int pid = suspendAndFork();//todo pid是啥意思
            if (pid == 0) {
                // Child process
                Debug.dumpHprofData(path);
                exitProcess();
            } else if (pid > 0) {
                // Parent process
                dumpRes = resumeAndWait(pid);//todo
                MonitorLog.i(TAG, "notify from pid " + pid);
            }
        } catch (IOException e) {
            MonitorLog.e(TAG, "dump failed caused by " + e.toString());
            e.printStackTrace();
        }
        return dumpRes;
    }

    /**
     * Init before do dump.
     */
    private native void init();

    /**
     * Suspend the whole ART, and then fork a process for dumping hprof.
     *
     * @return return value of fork
     */
    private native int suspendAndFork();//todo 返回

    /**
     * Resume the whole ART, and then wait child process to notify.
     *
     * @param pid pid of child process.
     */
    private native boolean resumeAndWait(int pid);//todo

    /**
     * Exit current process.
     */
    private native void exitProcess();
}

17.AnalysisReceiver 监听结果

//ResultReceiver todo
class AnalysisReceiver : ResultReceiver(null) {
  private var mResultCallBack: ResultCallBack? = null

  fun setResultCallBack(resultCallBack: ResultCallBack?) {
    mResultCallBack = resultCallBack
  }

  override fun onReceiveResult(resultCode: Int, resultData: Bundle?) {
    super.onReceiveResult(resultCode, resultData)
    if (mResultCallBack != null) {
      if (resultCode == RESULT_CODE_OK) {
        mResultCallBack!!.onSuccess()
      } else {
        mResultCallBack!!.onError()
      }
    }
  }

  interface ResultCallBack {
    fun onSuccess()
    fun onError()
  }

  companion object {
    const val RESULT_CODE_OK = 1001
    const val RESULT_CODE_FAIL = 1002
  }
}

18.FastHugeMemoryOOMTracker大于90%的时候强制dump,短时间增长350M的时候强制dump

class FastHugeMemoryOOMTracker : OOMTracker() {

    companion object {
        private const val TAG = "OOMMonitor_FastHugeMemoryTracker"
        private const val REASON_HIGH_WATERMARK = "high_watermark"//高水位
        private const val REASON_HUGE_DELTA = "delta"//大块内存
    }

    private var mDumpReason = ""

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

        // 高危阈值直接触发dump分析
        if (javaHeap.rate > monitorConfig.forceDumpJavaHeapMaxThreshold) {//大于90%的时候强制dump
            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)) {//增加了350M直接dump
            mDumpReason = REASON_HUGE_DELTA
            MonitorLog.i(TAG, "[meet condition] fast huge memory allocated detected, " +
                    "over the delta threshold!")
            return true
        }

        return false
    }

    override fun reset() = Unit

    override fun reason() = "reason_fast_huge_$mDumpReason"
}

19.FdOOMTracker fd大于1000,并且一直维持在高位,降的不明显

class FdOOMTracker : OOMTracker() {
    companion object {
        private const val TAG = "OOMMonitor_FdOOMTracker"

        private const val FD_COUNT_THRESHOLD_GAP = 50 //FD连续值递增浮动范围50
    }

    private var mLastFdCount = 0
    private var mOverThresholdCount = 0

    override fun track(): Boolean {
        val fdCount = getFdCount()
      //大于1000,并且一直维持在高位,降的不明显
        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
    }

    override fun reset() {
        mLastFdCount = 0
        mOverThresholdCount = 0
    }

    override fun reason() = "reason_fd_oom"

    private fun getFdCount(): Int {
        return File("/proc/self/fd").listFiles()?.size ?: 0
    }

  //打印fd信息
    private fun dumpFdIfNeed() {
        MonitorLog.i(TAG, "over threshold dumpFdIfNeed")

        if (mOverThresholdCount > monitorConfig.maxOverThresholdCount) return

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return

        val fdNames = runCatching { File("/proc/self/fd").listFiles() }
                .getOrElse {
                    MonitorLog.i(TAG, "/proc/self/fd child files is empty")

                    return@getOrElse emptyArray()
                }
                ?.map { file ->
                  //todo readlink
                    runCatching { Os.readlink(file.path) }.getOrElse { "failed to read link ${file.path}" }
                }
                ?: emptyList()

        OOMFileManager.createDumpFile(OOMFileManager.fdDumpDir)
                .run {
                    runCatching { writeText(fdNames.sorted().joinToString(separator = ",")) }
                }
    }
}

20.HeapOOMTracker heap处于高位,并且一直处于高位降的不明显

class HeapOOMTracker : OOMTracker() {
    companion object {
        private const val TAG = "OOMMonitor_HeapOOMTracker"

        private const val HEAP_RATIO_THRESHOLD_GAP = 0.05f
    }

    private var mLastHeapRatio = 0.0f
    private var mOverThresholdCount = 0

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

        //heapThreshold
        //     maxMem >= 512 - 10 -> 0.8f
        //     maxMem >= 256 - 10 -> 0.85f
        //     else -> 0.9f
        //处于高位,并且一直处于高位降的不明显
        if (heapRatio > monitorConfig.heapThreshold
                && heapRatio >= mLastHeapRatio - HEAP_RATIO_THRESHOLD_GAP) {

            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
    }

    override fun reset() {
        mLastHeapRatio = 0.0f
        mOverThresholdCount = 0
    }

    override fun reason() = "reason_heap_oom"
}

21.PhysicalMemoryOOMTracker 看着没啥用

//看着没啥用
class PhysicalMemoryOOMTracker : OOMTracker() {

  companion object {
    private const val TAG: String = "OOMMonitor_PhysicalMemoryTracker"
  }

  override fun track(): Boolean {
    val info = SystemInfo.memInfo
    when {
      //0.05
      info.rate < monitorConfig.deviceMemoryThreshold -> {
        MonitorLog.e(TAG, "oom meminfo.rate < " +
            "${monitorConfig.deviceMemoryThreshold * 100}%")
        //return true //先只是上传,不真实触发dump
      }

      info.rate < 0.10f -> {
        MonitorLog.i(TAG, "oom meminfo.rate < 10.0%")
      }

      info.rate < 0.15f -> {
        MonitorLog.i(TAG, "oom meminfo.rate < 15.0%")
      }

      info.rate < 0.20f -> {
        MonitorLog.i(TAG, "oom meminfo.rate < 20.0%")
      }

      info.rate < 0.30f -> {
        MonitorLog.i(TAG, "oom meminfo.rate < 30.0%")
      }
    }
    return false
  }

  override fun reset() = Unit

  override fun reason() = "reason_lmk_oom"

}

22.ThreadOOMTracker线程数量达到阈值,处于高位不下降

class ThreadOOMTracker : OOMTracker() {
    companion object {
        private const val TAG = "OOMMonitor_ThreadOOMTracker"

        private const val THREAD_COUNT_THRESHOLD_GAP = 50 //Thread连续值递增浮动范围50
    }

    private var mLastThreadCount = 0
    private var mOverThresholdCount = 0

    override fun track(): Boolean {
        val threadCount = getThreadCount()
      //华为的EMUI 小于O版本的,阈值450,
//      if (MonitorBuildConfig.ROM == "EMUI" && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) {
//        450
//      } else {
//        750
//      }
      //大于阈值,并且下降不明显,处于高位
        if (threadCount > monitorConfig.threadThreshold
                && threadCount >= mLastThreadCount - THREAD_COUNT_THRESHOLD_GAP) {
            mOverThresholdCount++

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

            dumpThreadIfNeed()
        } else {
            reset()
        }

        mLastThreadCount = threadCount

        return mOverThresholdCount >= monitorConfig.maxOverThresholdCount
    }

    override fun reset() {
        mLastThreadCount = 0
        mOverThresholdCount = 0
    }

    override fun reason() = "reason_thread_oom"

    private fun getThreadCount(): Int {
        return SystemInfo.procStatus.thread
    }

  //打印线程名
    private fun dumpThreadIfNeed() {
        MonitorLog.i(TAG, "over threshold dumpThreadIfNeed")

        if (mOverThresholdCount > monitorConfig.maxOverThresholdCount) return

        val threadNames = runCatching { File("/proc/self/task").listFiles() }
                .getOrElse {
                    MonitorLog.i(TAG, "/proc/self/task child files is empty")

                    return@getOrElse emptyArray()
                }
                ?.map {
                    runCatching { File(it, "comm").readText() }.getOrElse { "failed to read $it/comm" }
                }
                ?.map {
                    if (it.endsWith("\n")) it.substring(0, it.length - 1) else it
                }
                ?: emptyList()

        MonitorLog.i(TAG, "threadNames = $threadNames")

        OOMFileManager.createDumpFile(OOMFileManager.threadDumpDir)
                .run {
                    runCatching { writeText(threadNames.joinToString(separator = ",")) }
                }
    }
}

23.OOMMonitor 根据5个tracker来判断是否需要dump分析,启动分析

object OOMMonitor : LoopMonitor<OOMMonitorConfig>(), LifecycleEventObserver {
    //region 参数
    private const val TAG = "OOMMonitor"

    private val mOOMTrackers = mutableListOf(
            HeapOOMTracker(), ThreadOOMTracker(), FdOOMTracker(),
            PhysicalMemoryOOMTracker(), FastHugeMemoryOOMTracker())//有5个tracker
    private val mTrackReasons = mutableListOf<String>()//放上边5个tracker需要dump的原因

    private var mMonitorInitTime = 0L//监控开始时间

    private var mForegroundPendingRunnables = mutableListOf<Runnable>()//前台才执行startAnalysisService

    @Volatile
    private var mIsLoopStarted = false//todo

    @Volatile
    private var mIsLoopPendingStart = false//todo 这里为什么mIsLoopPendingStart,startLoop要手动调用??

    @Volatile
    private var mHasDumped = false // 每次启动周期里只dump一次 Only trigger one time in process running lifecycle.

    @Volatile
    private var mHasProcessOldHprof = false // 处理完久的dump文件 Only trigger one time in process running lifecycle.
    //endregion

    //region step1 初始化
    override fun init(commonConfig: CommonConfig, monitorConfig: OOMMonitorConfig) {
        super.init(commonConfig, monitorConfig)

        mMonitorInitTime = SystemClock.elapsedRealtime()

        OOMPreferenceManager.init(commonConfig.sharedPreferencesInvoker)
        OOMFileManager.init(commonConfig.rootFileInvoker)

        for (oomTracker in mOOMTrackers) {
            oomTracker.init(commonConfig, monitorConfig)
        }

        //注册监听
        getApplication().registerProcessLifecycleObserver(this)
    }
    //endregion

    //region step 2 startLoop todo 要手动启动吗
    override fun startLoop(clearQueue: Boolean, postAtFront: Boolean, delayMillis: Long) {
        throwIfNotInitialized { return }

        if (!isMainProcess()) {
            return
        }

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

        if (mIsLoopStarted) {
            return
        }
        mIsLoopStarted = true

        super.startLoop(clearQueue, postAtFront, delayMillis)
        //处理old
        getLoopHandler().postDelayed({ async { processOldHprofFile() } }, delayMillis)
    }

    //step 2.1 processOldHprofFile
    private fun processOldHprofFile() {
        MonitorLog.i(TAG, "processHprofFile")
        if (mHasProcessOldHprof) {
            return
        }
        mHasProcessOldHprof = true
        reAnalysisHprof()
        manualDumpHprof()
    }

    private fun reAnalysisHprof() {
        for (file in hprofAnalysisDir.listFiles().orEmpty()) {
            if (!file.exists()) continue

            if (!file.name.startsWith(MonitorBuildConfig.VERSION_NAME)) {
                MonitorLog.i(TAG, "delete other version files ${file.name}")
                file.delete()
                continue
            }

            if (file.canonicalPath.endsWith(".hprof")) {
                val jsonFile = File(file.canonicalPath.replace(".hprof", ".json"))
                if (!jsonFile.exists()) {
                    MonitorLog.i(TAG, "create json file and then start service")
                    jsonFile.createNewFile()
                    //启动分析
                    startAnalysisService(file, jsonFile, "reanalysis")
                } else {
                    MonitorLog.i(TAG,
                            if (jsonFile.length() == 0L) "last analysis isn't succeed, delete file"
                            else "delete old files", true)
                    jsonFile.delete()
                    file.delete()
                }
            }
        }
    }

    //在processOldHprofFile里调用,将久的文件交给hprofUploader处理
    private fun manualDumpHprof() {
        for (hprofFile in manualDumpDir.listFiles().orEmpty()) {
            MonitorLog.i(TAG, "manualDumpHprof upload:${hprofFile.absolutePath}")
            monitorConfig.hprofUploader?.upload(hprofFile, OOMHprofUploader.HprofType.STRIPPED)
        }
    }
    //endregion

    override fun stopLoop() {
        throwIfNotInitialized { return }

        if (!isMainProcess()) {
            return
        }

        super.stopLoop()

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

        mIsLoopStarted = false
    }

    override fun getLoopInterval() = monitorConfig.loopInterval

    //region step 3 trackOOM
    override fun call(): LoopState {
        //支持sdk范围
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
                || Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
            return LoopState.Terminate
        }

        //
        if (mHasDumped) {//一次运行只能一次
            return LoopState.Terminate
        }

        return trackOOM()
    }

    //5次
    private fun isExceedAnalysisTimes(): Boolean {
        MonitorLog.i(TAG, "OOMPreferenceManager.getAnalysisTimes:${OOMPreferenceManager.getAnalysisTimes()}")

        if (MonitorBuildConfig.DEBUG) {
            return false
        }

        return (OOMPreferenceManager.getAnalysisTimes() > monitorConfig.analysisMaxTimesPerVersion)
                .also { if (it) MonitorLog.e(TAG, "current version is out of max analysis times!") }
    }

    //每个版本的前15天才分析,超过这个时间段不再dump
    private fun isExceedAnalysisPeriod(): Boolean {
        MonitorLog.i(TAG, "OOMPreferenceManager.getFirstAnalysisTime():" + OOMPreferenceManager.getFirstLaunchTime())

        if (MonitorBuildConfig.DEBUG) {
            return false
        }

        val analysisPeriod = System.currentTimeMillis() - OOMPreferenceManager.getFirstLaunchTime()

        return (analysisPeriod > monitorConfig.analysisPeriodPerVersion)
                .also { if (it) MonitorLog.e(TAG, "current version is out of max analysis period!") }
    }

    private fun trackOOM(): LoopState {
        SystemInfo.refresh()

        mTrackReasons.clear()
        for (oomTracker in mOOMTrackers) {
            if (oomTracker.track()) {
                mTrackReasons.add(oomTracker.reason())
            }
        }

        if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {
            //每个版本的前15天才分析,超过这个时间段不再dump
            //每个版本最多分析5次
            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
    }

    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)
            val hprofFile = OOMFileManager.createHprofAnalysisFile(date).apply {
                createNewFile()
                setWritable(true)
                setReadable(true)
            }

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

            ForkJvmHeapDumper().run {
                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())
        }.onFailure {
            it.printStackTrace()

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

    private fun startAnalysisService(
            hprofFile: File,
            jsonFile: File,
            reason: String
    ) {
        if (hprofFile.length() == 0L) {
            hprofFile.delete()
            MonitorLog.i(TAG, "hprof file size 0", true)
            return
        }

        if (!getApplication().isForeground) {
            //todo 为啥在前台才行
            MonitorLog.e(TAG, "try startAnalysisService, but not foreground")
            mForegroundPendingRunnables.add(Runnable { startAnalysisService(hprofFile, jsonFile, reason) })
            return
        }

        OOMPreferenceManager.increaseAnalysisTimes()

        val extraData = AnalysisExtraData().apply {
            this.reason = reason
            this.currentPage = getApplication().currentActivity?.localClassName.orEmpty()
            this.usageSeconds = "${(SystemClock.elapsedRealtime() - mMonitorInitTime) / 1000}"
        }

        //开启分析
        HeapAnalysisService.startAnalysisService(
                getApplication(),
                hprofFile.canonicalPath,
                jsonFile.canonicalPath,
                extraData,
                //监听回调
                object : AnalysisReceiver.ResultCallBack {
                    override fun onError() {
                        MonitorLog.e(TAG, "heap analysis error, do file delete", true)

                        hprofFile.delete()
                        jsonFile.delete()
                    }

                    override fun onSuccess() {
                        MonitorLog.i(TAG, "heap analysis success, do upload", true)

                        val content = jsonFile.readText()

                        MonitorLogger.addExceptionEvent(content, Logger.ExceptionType.OOM_STACKS)

                        monitorConfig.reportUploader?.upload(jsonFile, content)
                        monitorConfig.hprofUploader?.upload(hprofFile, OOMHprofUploader.HprofType.ORIGIN)
                    }
                })
    }
    //endregion

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
        when (event) {
            Lifecycle.Event.ON_START -> {
                //todo 这里为什么mIsLoopPendingStart,startLoop要手动调用??
                if (!mHasDumped && mIsLoopPendingStart) {
                    MonitorLog.i(TAG, "foreground")
                    startLoop()
                }
                //在后台的时候不分析,为啥?  todo
                mForegroundPendingRunnables.forEach { it.run() }
                mForegroundPendingRunnables.clear()
            }
            Lifecycle.Event.ON_STOP -> {
                mIsLoopPendingStart = mIsLoopStarted
                MonitorLog.i(TAG, "background")
                stopLoop()
            }
            else -> Unit
        }
    }
}

HeapAnalysisService 分析

* 1.已经destroyed和finished的activity
* 2.已经fragment manager为空的fragment
* 3.已经destroyed的window
* 4.超过阈值大小的bitmap
* 5.超过阈值大小的基本类型数组
* 6.超过阈值大小的对象个数的任意class
class HeapAnalysisService : IntentService("HeapAnalysisService") {
    companion object {
        private const val TAG = "OOMMonitor_HeapAnalysisService"

        private const val OOM_ANALYSIS_TAG = "OOMMonitor"
        private const val OOM_ANALYSIS_EXCEPTION_TAG = "OOMMonitor_Exception"

        //Activity->ContextThemeWrapper->ContextWrapper->Context->Object
        private const val ACTIVITY_CLASS_NAME = "android.app.Activity"

        //Bitmap->Object
        //Exception: Some OPPO devices
        const val BITMAP_CLASS_NAME = "android.graphics.Bitmap"

        //Fragment->Object
        private const val NATIVE_FRAGMENT_CLASS_NAME = "android.app.Fragment"

        // native android Fragment, deprecated as of API 28.
        private const val SUPPORT_FRAGMENT_CLASS_NAME = "android.support.v4.app.Fragment"

        // pre-androidx, support library version of the Fragment implementation.
        private const val ANDROIDX_FRAGMENT_CLASS_NAME = "androidx.fragment.app.Fragment"

        // androidx version of the Fragment implementation
        //Window->Object
        private const val WINDOW_CLASS_NAME = "android.view.Window"

        //NativeAllocationRegistry todo 干哈的
        private const val NATIVE_ALLOCATION_CLASS_NAME = "libcore.util.NativeAllocationRegistry"

        //todo 干哈的
        private const val NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME = "libcore.util.NativeAllocationRegistry\$CleanerThunk"

        private const val FINISHED_FIELD_NAME = "mFinished"
        private const val DESTROYED_FIELD_NAME = "mDestroyed"

        //todo 干哈的
        private const val FRAGMENT_MANAGER_FIELD_NAME = "mFragmentManager"

        //todo 干哈的
        private const val FRAGMENT_MCALLED_FIELD_NAME = "mCalled"

        private const val DEFAULT_BIG_PRIMITIVE_ARRAY = 256 * 1024//256kb? 基本数据类型数组 todo
        private const val DEFAULT_BIG_BITMAP = 768 * 1366 + 1   //大图片 宽*高
        private const val DEFAULT_BIG_OBJECT_ARRAY = 256 * 1024 //对象数组限制,256kb?todo
        private const val SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD = 45 ////单一类的泄漏对象只找45个,超过的忽略

        annotation class Info {
            companion object {
                internal const val HPROF_FILE = "HPROF_FILE"
                internal const val JSON_FILE = "JSON_FILE"
                internal const val ROOT_PATH = "ROOT_PATH"
                internal const val RESULT_RECEIVER = "RESULT_RECEIVER"

                internal const val JAVA_MAX_MEM = "JAVA_MAX_MEM"
                internal const val JAVA_USED_MEM = "JAVA_USED_MEM"
                internal const val DEVICE_MAX_MEM = "DEVICE_MAX_MEM"
                internal const val DEVICE_AVA_MEM = "DEVICE_AVA_MEM"
                internal const val VSS = "VSS"
                internal const val PSS = "PSS"
                internal const val RSS = "RSS"
                internal const val FD = "FD"
                internal const val THREAD = "THREAD"
                internal const val SDK = "SDK"
                internal const val MANUFACTURE = "MANUFACTURE"
                internal const val MODEL = "MODEL"
                internal const val TIME = "TIME"
                internal const val REASON = "REASON"
                internal const val USAGE_TIME = "USAGE_TIME"
                internal const val CURRENT_PAGE = "CURRENT_PAGE"
            }
        }

        fun startAnalysisService(context: Context, hprofFile: String?, jsonFile: String?,
                                 extraData: AnalysisExtraData, resultCallBack: AnalysisReceiver.ResultCallBack?) {
            MonitorLog.i(TAG, "startAnalysisService")

            val analysisReceiver = AnalysisReceiver().apply {
                setResultCallBack(resultCallBack)
            }

            val intent = Intent(context, HeapAnalysisService::class.java).apply {
                putExtra(Info.HPROF_FILE, hprofFile)
                putExtra(Info.JSON_FILE, jsonFile)
                putExtra(Info.ROOT_PATH, OOMFileManager.rootDir.absolutePath)
                putExtra(Info.RESULT_RECEIVER, analysisReceiver)

                putExtra(Info.JAVA_MAX_MEM, BYTE.toMB(javaHeap.max).toString())
                putExtra(Info.JAVA_USED_MEM, BYTE.toMB(javaHeap.total - javaHeap.free).toString())
                putExtra(Info.DEVICE_MAX_MEM, KB.toMB(memInfo.totalInKb).toString())
                putExtra(Info.DEVICE_AVA_MEM, KB.toMB(memInfo.availableInKb).toString())

                putExtra(Info.FD, (File("/proc/self/fd").listFiles()?.size ?: 0).toString())

                val pss = Debug.getPss()
                MonitorLog.i(TAG, "startAnalysisService get Pss:${pss}")
                putExtra(Info.PSS, KB.toMB(pss).toString() + "mb")//这个有用

                putExtra(Info.VSS, KB.toMB(procStatus.vssInKb).toString() + "mb")
                putExtra(Info.RSS, KB.toMB(procStatus.rssInKb).toString() + "mb")

                if (extraData.reason != null) {
                    putExtra(Info.REASON, extraData.reason)
                }
                if (extraData.currentPage != null) {
                    putExtra(Info.CURRENT_PAGE, extraData.currentPage)
                }
                if (extraData.usageSeconds != null) {
                    putExtra(Info.USAGE_TIME, extraData.usageSeconds)
                }
            }

            context.startService(intent)
        }
    }

    private lateinit var mHeapGraph: HeapGraph

    private val mLeakModel = HeapReport()
    private val mLeakingObjectIds = mutableSetOf<Long>()
    private val mLeakReasonTable = mutableMapOf<Long, String>()

    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 {
            buildIndex(hprofFile)//第一步,索引hprof
        }.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)//第二步,初始化json,filterLeakingObjects,查找泄漏对象

        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 {
            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
        }

        fillJsonFile(jsonFile)//第四步,填充json文件,发送

        resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK, null)

        System.exit(0)
    }

    //第一步,索引hprof
    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 {
            //创建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")
        }
    }

    private fun buildJson(intent: Intent?) {
        mLeakModel.runningInfo = HeapReport.RunningInfo().apply {
            jvmMax = intent?.getStringExtra(Info.JAVA_MAX_MEM)
            jvmUsed = intent?.getStringExtra(Info.JAVA_USED_MEM)
            threadCount = intent?.getStringExtra(Info.THREAD)
            fdCount = intent?.getStringExtra(Info.FD)

            vss = intent?.getStringExtra(Info.VSS)
            pss = intent?.getStringExtra(Info.PSS)
            rss = intent?.getStringExtra(Info.RSS)

            sdkInt = intent?.getStringExtra(Info.SDK)
            manufacture = intent?.getStringExtra(Info.MANUFACTURE)
            buildModel = intent?.getStringExtra(Info.MODEL)

            usageSeconds = intent?.getStringExtra(Info.USAGE_TIME)
            currentPage = intent?.getStringExtra(Info.CURRENT_PAGE)
            nowTime = intent?.getStringExtra(Info.TIME)

            deviceMemTotal = intent?.getStringExtra(Info.DEVICE_MAX_MEM)
            deviceMemAvaliable = intent?.getStringExtra(Info.DEVICE_AVA_MEM)

            dumpReason = intent?.getStringExtra(Info.REASON)

            MonitorLog.i(TAG, "handle Intent, fdCount:${fdCount} pss:${pss} rss:${rss} vss:${vss} " +
                    "threadCount:${threadCount}")

            fdList = createDumpFile(fdDumpDir).takeIf { it.exists() }?.readLines()
            threadList = createDumpFile(threadDumpDir).takeIf { it.exists() }?.readLines()

            createDumpFile(fdDumpDir).delete()//删除fd dump文件
            createDumpFile(threadDumpDir).delete()//删除thread dump文件
        }
    }

    /**
     * 遍历镜像所有class查找
     *
     * 计算gc path:
     * 1.已经destroyed和finished的activity
     * 2.已经fragment manager为空的fragment
     * 3.已经destroyed的window
     * 4.超过阈值大小的bitmap
     * 5.超过阈值大小的基本类型数组
     * 6.超过阈值大小的对象个数的任意class
     *
     *
     * 记录关键类:
     * 对象数量
     * 1.基本类型数组
     * 2.Bitmap
     * 3.NativeAllocationRegistry
     * 4.超过阈值大小的对象的任意class
     *
     *
     * 记录大对象:
     * 对象大小
     * 1.Bitmap
     * 2.基本类型数组
     *
     * //第二步,初始化json,filterLeakingObjects,查找泄漏对象
     */
    private fun filterLeakingObjects() {
        val startTime = System.currentTimeMillis()
        MonitorLog.i(TAG, "filterLeakingObjects " + Thread.currentThread())

        //查找activity类
        val activityHeapClass = mHeapGraph.findClassByName(ACTIVITY_CLASS_NAME)
        //查找fragment类
        val fragmentHeapClass = mHeapGraph.findClassByName(ANDROIDX_FRAGMENT_CLASS_NAME)
                ?: mHeapGraph.findClassByName(NATIVE_FRAGMENT_CLASS_NAME)
                ?: mHeapGraph.findClassByName(SUPPORT_FRAGMENT_CLASS_NAME)
        //查找bitmap类
        val bitmapHeapClass = mHeapGraph.findClassByName(BITMAP_CLASS_NAME)
        //todo这俩干哈的
        val nativeAllocationHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLASS_NAME)
        val nativeAllocationThunkHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME)
        //查找window
        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 {
                // List<HeapObject.HeapClass>
                //类层次结构从这个类(包含)开始到 [Object] 类(包含)结束。
                val classHierarchyList = instance.instanceClass.classHierarchy.toList()
                //first除了object下一个
                val first = classHierarchyList.getOrNull(classHierarchyList.size - 2)?.objectId
                        ?: 0L
                //对于activity,ContextThemeWrapper,参考以下代码
                //        //Activity->ContextThemeWrapper->ContextWrapper->Context->Object
                //        private const val ACTIVITY_CLASS_NAME = "android.app.Activity"
                //其他像fragment,bitmap,window都是继承自object
                val second = classHierarchyList.getOrNull(classHierarchyList.size - 5)?.objectId
                        ?: 0L

                Pair(first, second).also { classHierarchyMap[instanceClassId] = it }
            }

            //Activity
            if (activityHeapClass?.objectId == superId4) {
                //找到两个field的值
                val destroyField = instance[ACTIVITY_CLASS_NAME, DESTROYED_FIELD_NAME]!!
                val finishedField = instance[ACTIVITY_CLASS_NAME, FINISHED_FIELD_NAME]!!
                //destroyField是true,finishedField也是true
                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))
                    //单一类的泄漏对象只找45个,超过的忽略
                    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)
                    //单一类的泄漏对象只找45个,超过的忽略
                    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) {//大图片宽高,768 * 1366 + 1
                    ////计算对象个数,和泄漏对象个数
                    val objectCounter = updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, true)
                    MonitorLog.e(TAG, "suspect leak! bitmap name: ${instance.instanceClassName}" +
                            " width: ${width} height:${height}")
                    //单一类的泄漏对象只找45个,超过的忽略
                    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) {//256kb
                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) {//256 * 1024
                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)
    }

    ////第三步,查找泄漏对象的引用用链
    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)
                }
        )

        //AndroidReferenceMatchers.appDefaults
        //用于模式匹配堆中已知的引用模式,要么忽略它们([IgnoredReferenceMatcher]),
        //要么将它们标记为库泄漏([LibraryLeakReferenceMatcher
        val findLeakInput = FindLeakInput(mHeapGraph, AndroidReferenceMatchers.appDefaults,
                false, mutableListOf())

        //这是啥语法,调用heapAnalyzer的findLeaks
        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
            )
            //applicationLeak.leakTraces 共享相同泄漏签名的一组泄漏跟踪。
            val (gcRootType, referencePath, leakTraceObject) = applicationLeak.leakTraces[0]
            //GcRootType的描述
            val gcRoot = gcRootType.description
            //val labels: Array<String> 在分析期间计算的标签。 标签提供了有助于了解泄漏跟踪对象状态的额外信息。
            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 {
                        //applicationLeak.leakTraces 共享相同泄漏签名的一组泄漏跟踪。
                        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,todo,leakTraceObject他自身是在referencePath的最后一个吧,这里多加了一遍
            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 //todo这里为啥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)
    }

    //第四步,填充json文件,发送
    private fun fillJsonFile(jsonFile: String?) {
        val json = Gson().toJson(mLeakModel)

        try {
            jsonFile?.let { File(it) }?.writeText(json)

            MonitorLog.i(OOM_ANALYSIS_TAG, "JSON write success: $json")
        } catch (e: IOException) {
            e.printStackTrace()
            MonitorLog.i(OOM_ANALYSIS_TAG, "JSON write exception: $json", true)
        }
    }

    ////计算对象个数,和泄漏对象个数
    private fun updateClassObjectCounterMap(
            classObCountMap: MutableMap<Long, ObjectCounter>,
            instanceClassId: Long,
            isLeak: Boolean
    ): ObjectCounter {
        val objectCounter = classObCountMap[instanceClassId] ?: ObjectCounter().also {
            classObCountMap[instanceClassId] = it
        }

        objectCounter.allCnt++

        if (isLeak) {
            objectCounter.leakCnt++
        }

        return objectCounter
    }

    class ObjectCounter {
        var allCnt = 0
        var leakCnt = 0
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值