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