内存优化之-Koom源码分析
Java层泄漏
源码分析
1.koom-demo的JavaLeakTestActivity作为入口开始分析
JavaLeakTestActivity onClick方法
case R.id.btn_make_java_leak:
showJavaLeakHint();
/*
* Init OOMMonitor
*/
OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication()); //代码1,做一些初始化的配置
OOMMonitor.INSTANCE.startLoop(true, false,5_000L); //代码2
/*
* Make some leaks for test!
*/
LeakMaker.makeLeak(this);
break;
代码1做一些初始化的配置,代码2则是开启子线程进行轮训
2.继续分析上述两处代码
OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication());
OOMMonitor.INSTANCE.startLoop(true, false,5_000L);
OOMMonitorInitTask.INSTANCE.init(JavaLeakTestActivity.this.getApplication());
override fun init(application: Application) {
val config = OOMMonitorConfig.Builder() //代码3
.setThreadThreshold(50) //50 only for test! Please use default value!
.setFdThreshold(300) // 300 only for test! Please use default value!
.setHeapThreshold(0.9f) // 0.9f for test! Please use default value!
.setVssSizeThreshold(1_000_000) // 1_000_000 for test! Please use default value!
.setMaxOverThresholdCount(1) // 1 for test! Please use default value!
.setAnalysisMaxTimesPerVersion(3) // Consider use default value!
.setAnalysisPeriodPerVersion(15 * 24 * 60 * 60 * 1000) // Consider use default value!
.setLoopInterval(5_000) // 5_000 for test! Please use default value!
.setEnableHprofDumpAnalysis(true)
.setHprofUploader(object : OOMHprofUploader {
override fun upload(file: File, type: OOMHprofUploader.HprofType) {
MonitorLog.e("OOMMonitor", "todo, upload hprof ${file.name} if necessary")
}
})
.setReportUploader(object : OOMReportUploader {
override fun upload(file: File, content: String) {
MonitorLog.i("OOMMonitor", content)
MonitorLog.e("OOMMonitor", "todo, upload report ${file.name} if necessary")
}
})
.build()
MonitorManager.addMonitorConfig(config) //代码4
}
代码3处设置一些初始化参数,以及回调(具体参数后边使用的时候会解释) ,调用build构建OOMMonitorConfig对象(OOMMonitorConfig继承自MonitorConfig)
具体分析下代码4处MonitorManager.addMonitorConfig(config)
fun <M : MonitorConfig<*>> addMonitorConfig(config: M) = apply {
var supperType: Type? = config.javaClass.genericSuperclass
while (supperType is Class<*>) {
supperType = supperType.genericSuperclass
}
if (supperType !is ParameterizedType) { // 代码5 判断是否是参数化类型
throw java.lang.RuntimeException("config must be parameterized")
}
val monitorType = supperType.actualTypeArguments[0] as Class<Monitor<M>>
if (MONITOR_MAP.containsKey(monitorType)) {
return@apply
}
val monitor = try { //代码6
monitorType.getDeclaredField("INSTANCE").get(null) as Monitor<M>
} catch (e: Throwable) {
monitorType.newInstance() as Monitor<M>
}
MONITOR_MAP[monitorType] = monitor //反射获取monitor对象,存储在map
monitor.init(commonConfig, config). //代码7
monitor.logMonitorEvent()
}
上述代码5,获取最上层的父类对象,判断是否是参数化对象
代码6获取范型对象的第一个参数,构造成对象,存储到MONITOR_MAP集合,并且调用对象的init进行初始化,即代码3构造的MonitorConfig对象,OOMMonitor
3.然后继续分析OOMMonitor.INSTANCE.startLoop(true, false,5_000L);
override fun startLoop(clearQueue: Boolean, postAtFront: Boolean, delayMillis: Long) { //true false 5_000L
throwIfNotInitialized { return }
if (!isMainProcess()) { //判断是否是主进程
return
}
MonitorLog.i(TAG, "startLoop()")
if (mIsLoopStarted) {
return
}
mIsLoopStarted = true
super.startLoop(clearQueue, postAtFront, delayMillis) //代码8 子线程执行call
getLoopHandler().postDelayed({ async { processOldHprofFile() } }, delayMillis)
}
代码8则是在子线程轮训调用OOMMonitor.call()方法
OOMMonitor
override fun call(): LoopState {
if (!sdkVersionMatch()) {
return LoopState.Terminate
}
if (mHasDumped) {
return LoopState.Terminate
}
return trackOOM() //代码9
}
代码9处trackOOM
private fun trackOOM(): LoopState {
SystemInfo.refresh() //代码10 收集内存信息
mTrackReasons.clear() //集合clear
for (oomTracker in mOOMTrackers) { //代码11 遍历trackers
if (oomTracker.track()) { //调用tracker
mTrackReasons.add(oomTracker.reason())
}
}
if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {
if (isExceedAnalysisPeriod() || isExceedAnalysisTimes()) {
MonitorLog.e(TAG, "Triggered, but exceed analysis times or period!")
} else {
async {
MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")
dumpAndAnalysis() //内存dump
}
}
return LoopState.Terminate
}
return LoopState.Continue
}
代码10处手机一些系统的信息(java堆栈的最大内存,总内存,空闲的内存,使用的内存,内存使用率;虚拟地址空间大小,应用程序正在使用的物理内存的大小,共享使用该信号描述符的任务的个数,所有RAM大小,空闲物理内存,可用物理内存,内存的使用率)
代码11处,编译mOOMTrackers,调用集合的trace方法
private val mOOMTrackers = mutableListOf(
HeapOOMTracker(), ThreadOOMTracker(), FdOOMTracker(),
PhysicalMemoryOOMTracker(), FastHugeMemoryOOMTracker()
)
可以看到OOM集合,我们逐一分析
4.HeapOOMTracker
分析下HeapOOMTracker trace
override fun track(): Boolean { //堆oom判断
val heapRatio = SystemInfo.javaHeap.rate // 代码12 javaHeap 使用率
if (heapRatio > monitorConfig.heapThreshold
&& heapRatio >= mLastHeapRatio - HEAP_RATIO_THRESHOLD_GAP) { //代码13大于阀值并且是上涨的趋势
mOverThresholdCount++ //计数器++
MonitorLog.i(TAG,
"[meet condition] "
+ "overThresholdCount: $mOverThresholdCount"
+ ", heapRatio: $heapRatio"
+ ", usedMem: ${SizeUnit.BYTE.toMB(SystemInfo.javaHeap.used)}mb"
+ ", max: ${SizeUnit.BYTE.toMB(SystemInfo.javaHeap.max)}mb")
} else {
reset()
}
mLastHeapRatio = heapRatio
return mOverThresholdCount >= monitorConfig.maxOverThresholdCount //次数大于阀值 返回true
}
代码12,获取java堆内存的使用率,是在上述代码 SystemInfo.refresh() 获取的堆栈数据,
代码13,判断内存的使用率是否超过阀值,大于阀值(08%/85%/90%根据机型的内存),并且是上涨趋势的,并且次数超过三次,就会返回true,从而执行后边的dump流程
5.ThreadOOMTracker
ThreadOOMTracker
override fun track(): Boolean {
val threadCount = getThreadCount() //代码14处get 线程数量
if (threadCount > monitorConfig.threadThreshold
&& threadCount >= mLastThreadCount - THREAD_COUNT_THRESHOLD_GAP) { //代码15处判断线程数量是否达到阀值,and 比上次多50
mOverThresholdCount++
MonitorLog.i(TAG,
"[meet condition] "
+ "overThresholdCount:$mOverThresholdCount"
+ ", threadCount: $threadCount")
dumpThreadIfNeed()
} else {
reset()
}
mLastThreadCount = threadCount
return mOverThresholdCount >= monitorConfig.maxOverThresholdCount
}
代码14处获取线程数量,代码15处线程数量与阀值(根据机型450/750)进行对比,并且比上次多50,返回true
6.FdOOMTracker
override fun track(): Boolean {
val fdCount = getFdCount() //代码16获取文件描述符
if (fdCount > monitorConfig.fdThreshold && fdCount >= mLastFdCount - FD_COUNT_THRESHOLD_GAP) {
mOverThresholdCount++
MonitorLog.i(TAG,
"[meet condition] "
+ "overThresholdCount: $mOverThresholdCount"
+ ", fdCount: $fdCount")
dumpFdIfNeed()
} else {
reset()
}
mLastFdCount = fdCount
return mOverThresholdCount >= monitorConfig.maxOverThresholdCount
}
代码16处获取文件描述符的数量,然后与阀值进行比较(一般会设置为300),比上次多50,次数超过了阀值,则返回true
7 .PhysicalMemoryOOMTracker
物理内存的监控,目前还没有使用
8.FastHugeMemoryOOMTracker
override fun track(): Boolean {
val javaHeap = SystemInfo.javaHeap
// 高危阈值直接触发dump分析
if (javaHeap.rate > monitorConfig.forceDumpJavaHeapMaxThreshold) { //代码17,检测java堆内存的使用率
mDumpReason = REASON_HIGH_WATERMARK
MonitorLog.i(TAG, "[meet condition] fast huge memory allocated detected, " +
"high memory watermark, force dump analysis!")
return true
}
// 高差值直接dump
val lastJavaHeap = SystemInfo.lastJavaHeap
if (lastJavaHeap.max != 0L && javaHeap.used - lastJavaHeap.used
> SizeUnit.KB.toByte(monitorConfig.forceDumpJavaHeapDeltaThreshold)) {
mDumpReason = REASON_HUGE_DELTA
MonitorLog.i(TAG, "[meet condition] fast huge memory allocated detected, " +
"over the delta threshold!")
return true
}
return false
}
代码17 ,判断java堆内存使用是否查超过了阀值(默认0.9),或者比上次使用内存增长超过了阀值(默认350m),返回true
6.内存dump hprof
上述代码11处遍历OOMTracker对象,如果有一个trace返回true,dumpAndAnalysis
OOMMonitor
private fun trackOOM(): LoopState {
SystemInfo.refresh() //收集内存信息
mTrackReasons.clear() //集合clear
for (oomTracker in mOOMTrackers) { //遍历trackers
if (oomTracker.track()) { //调用tracker
mTrackReasons.add(oomTracker.reason())
}
}
if (mTrackReasons.isNotEmpty() && monitorConfig.enableHprofDumpAnalysis) {
if (isExceedAnalysisPeriod() || isExceedAnalysisTimes()) {
MonitorLog.e(TAG, "Triggered, but exceed analysis times or period!")
} else {
async {
MonitorLog.i(TAG, "mTrackReasons:${mTrackReasons}")
dumpAndAnalysis() //代码18内存dump
}
}
return LoopState.Terminate
}
return LoopState.Continue
}
代码18处,进行dump流程
OOMMonitor
private fun dumpAndAnalysis() {
MonitorLog.i(TAG, "dumpAndAnalysis");
runCatching {
if (!OOMFileManager.isSpaceEnough()) {
MonitorLog.e(TAG, "available space not enough", true)
return@runCatching
}
if (mHasDumped) {
return
}
mHasDumped = true
val date = Date()
val jsonFile = OOMFileManager.createJsonAnalysisFile(date) //代码19
val hprofFile = OOMFileManager.createHprofAnalysisFile(date).apply { //代码20
createNewFile()
setWritable(true)
setReadable(true)
}
MonitorLog.i(TAG, "hprof analysis dir:$hprofAnalysisDir")
ForkJvmHeapDumper.getInstance().run { //代码21
dump(hprofFile.absolutePath) //dump
}
MonitorLog.i(TAG, "end hprof dump", true)
Thread.sleep(1000) // make sure file synced to disk.
MonitorLog.i(TAG, "start hprof analysis")
startAnalysisService(hprofFile, jsonFile, mTrackReasons.joinToString()) //代码22开始分析
}.onFailure {
it.printStackTrace()
MonitorLog.i(TAG, "onJvmThreshold Exception " + it.message, true)
}
}
代码19 20 分别创建内存解析的json文件,hprof文件,
代码21,fork进行进行dump内存快照
代码22,分析内存快照
下边我们就来分析下如何dump内存快照以及内存快照的分析
ForkJvmHeapDumper
@Override
public synchronized boolean dump(String path) {
MonitorLog.i(TAG, "dump " + path);
if (!sdkVersionMatch()) {
throw new UnsupportedOperationException("dump failed caused by sdk version not supported!");
}
init();
if (!mLoadSuccess) {
MonitorLog.e(TAG, "dump failed caused by so not loaded!");
return false;
}
boolean dumpRes = false;
try {
MonitorLog.i(TAG, "before suspend and fork.");
int pid = suspendAndFork(); //代码23
if (pid == 0) {
// Child process
Debug.dumpHprofData(path); //代码24
exitProcess();
} else if (pid > 0) {
// Parent process
dumpRes = resumeAndWait(pid); //代码25
MonitorLog.i(TAG, "dump " + dumpRes + ", notify from pid " + pid);
}
} catch (IOException e) {
MonitorLog.e(TAG, "dump failed caused by " + e);
e.printStackTrace();
}
return dumpRes;
}
代码23 suspend当前进行的所有线程,然后执行fork操作
代码24调用系统提供的api debug.dumpHprofData dump内存快照
代码25是执行在主进程的代码,恢复主进程suspend的线程
这里有两个问题说明下:
1.为什么要suspend所有的进程?
答:多线程执行的情况下,调用fork函数,仅会将发起调用的线程复制到子进程中,其他线程均在子进程中立即停止并消失,但是父进程全局变量的状态以及所有的pthreads对象(如互斥量,条件变量等)都会在子进程得以保留。因此,子进程dump hprof的时候,SuspendAll触发暂停是永远得不到其他线程的返回结果,导致子进程阻塞卡死。
解决版本就是先在主进程执行suspendAll,ThreadList中保存的所有线程状态为suspend,之后fork,子进程dump hprof,由于子进程共享父进程的ThreadList全局变量,因此认为所有的线程已经处于suspend状态,避免了子进程dump时触发暂停等不到线程返回结果的情况,fork完毕后立刻执行ResumeAll恢复运行。
2.如何fork进程?
答:在android中,suspend相关代码被编译成libart.so(Art),使用dlsym动态加载Suspend方法
pid_t HprofDump::SuspendAndFork() {
KCHECKI(init_done_)
if (android_api_ < __ANDROID_API_R__) { //代码26
suspend_vm_fnc_();
} else if (android_api_ <= __ANDROID_API_S__) {
void *self = __get_tls()[TLS_SLOT_ART_THREAD_SELF];
sgc_constructor_fnc_((void *)sgc_instance_.get(), self, kGcCauseHprof,
kCollectorTypeHprof);
ssa_constructor_fnc_((void *)ssa_instance_.get(), LOG_TAG, true);
// avoid deadlock with child process
exclusive_unlock_fnc_(*mutator_lock_ptr_, self);
sgc_destructor_fnc_((void *)sgc_instance_.get());
}
pid_t pid = fork(); //代码27 fork一个进程
if (pid == 0) {
// Set timeout for child process
alarm(60); //SIGALRM
prctl(PR_SET_NAME, "forked-dump-process");
}
return pid;
}
代码26 调用libart.so方法,suspend所有的线程
代码27 fork进程
7.内存hprof分析
上述代码22,会开启service分析hprof,
HeapAnalysisService
override fun onHandleIntent(intent: Intent?) {
val resultReceiver = intent?.getParcelableExtra<ResultReceiver>(Info.RESULT_RECEIVER)
val hprofFile = intent?.getStringExtra(Info.HPROF_FILE)
val jsonFile = intent?.getStringExtra(Info.JSON_FILE)
val rootPath = intent?.getStringExtra(Info.ROOT_PATH)
OOMFileManager.init(rootPath)
kotlin.runCatching { //代码28
buildIndex(hprofFile)
}.onFailure {
it.printStackTrace()
MonitorLog.e(OOM_ANALYSIS_EXCEPTION_TAG, "build index exception " + it.message, true)
resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)
return
}
buildJson(intent) //代码29
kotlin.runCatching {
filterLeakingObjects() //代码30
}.onFailure {
MonitorLog.i(OOM_ANALYSIS_EXCEPTION_TAG, "find leak objects exception " + it.message, true)
resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)
return
}
kotlin.runCatching {
findPathsToGcRoot() //代码31
}.onFailure {
it.printStackTrace()
MonitorLog.i(OOM_ANALYSIS_EXCEPTION_TAG, "find gc path exception " + it.message, true)
resultReceiver?.send(AnalysisReceiver.RESULT_CODE_FAIL, null)
return
}
fillJsonFile(jsonFile)
resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK, null)
System.exit(0);
}
代码29 收集一些系统信息,存储到HeapReport.RunningInfo对象中,比如虚拟机堆内存信息,物理内存信息,虚拟内存信息,线程信息,fd信息
代码30,遍历镜像,找到泄漏的对象
以下代码分析流程很简单,大概总结下:
1.判断hprof存在的对象,如果是Activity,Activity对象的mDestoryed,mFinished都为true,则Activity泄漏
2.如果是Fragment,mFragmentManager为null,mCalled为true,泄漏
3.如果是Bitmap,mWidth,mHeight计算bitmap size大于阀值,大对象泄漏
4.基本类型数据,大于256m,大对象泄漏
5.对象数组,大于256m,大对象泄漏
private fun filterLeakingObjects() {
val startTime = System.currentTimeMillis()
MonitorLog.i(TAG, "filterLeakingObjects " + Thread.currentThread())
val activityHeapClass = mHeapGraph.findClassByName(ACTIVITY_CLASS_NAME) //activity
val fragmentHeapClass = mHeapGraph.findClassByName(ANDROIDX_FRAGMENT_CLASS_NAME) //fragment
?: mHeapGraph.findClassByName(NATIVE_FRAGMENT_CLASS_NAME)
?: mHeapGraph.findClassByName(SUPPORT_FRAGMENT_CLASS_NAME)
val bitmapHeapClass = mHeapGraph.findClassByName(BITMAP_CLASS_NAME) //bitmap
val nativeAllocationHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLASS_NAME) //
val nativeAllocationThunkHeapClass = mHeapGraph.findClassByName(NATIVE_ALLOCATION_CLEANER_THUNK_CLASS_NAME) //NativeAllocationRegistry
val windowClass = mHeapGraph.findClassByName(WINDOW_CLASS_NAME) //android.view.Window
//缓存classHierarchy,用于查找class的所有instance
val classHierarchyMap = mutableMapOf<Long, Pair<Long, Long>>()
//记录class objects数量
val classObjectCounterMap = mutableMapOf<Long, ObjectCounter>()
//遍历镜像的所有instance
for (instance in mHeapGraph.instances) {
if (instance.isPrimitiveWrapper) {
continue
}
//使用HashMap缓存及遍历两边classHierarchy,这2种方式加速查找instance是否是对应类实例
//superId1代表类的继承层次中倒数第一的id,0就是继承自object
//superId4代表类的继承层次中倒数第四的id
//类的继承关系,以AOSP代码为主,部分厂商入如OPPO Bitmap会做一些修改,这里先忽略
val instanceClassId = instance.instanceClassId
val (superId1, superId4) = if (classHierarchyMap[instanceClassId] != null) {
classHierarchyMap[instanceClassId]!!
} else {
val classHierarchyList = instance.instanceClass.classHierarchy.toList() //类的层次结构
val first = classHierarchyList.getOrNull(classHierarchyList.size - 2)?.objectId ?: 0L
val second = classHierarchyList.getOrNull(classHierarchyList.size - 5)?.objectId ?: 0L
Pair(first, second).also { classHierarchyMap[instanceClassId] = it }
}
//Activity
if (activityHeapClass?.objectId == superId4) { //instance 是 Activity
val destroyField = instance[ACTIVITY_CLASS_NAME, DESTROYED_FIELD_NAME]!! //get Activity mDestoryed
val finishedField = instance[ACTIVITY_CLASS_NAME, FINISHED_FIELD_NAME]!! //get Activity mFinished
if (destroyField.value.asBoolean!! || finishedField.value.asBoolean!!) { //判断这些字段
val objectCounter = updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, true) //更新泄漏对象的数量
MonitorLog.i(TAG, "activity name : " + instance.instanceClassName
+ " mDestroyed:" + destroyField.value.asBoolean
+ " mFinished:" + finishedField.value.asBoolean
+ " objectId:" + (instance.objectId and 0xffffffffL))
if (objectCounter.leakCnt <= SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD) {
mLeakingObjectIds.add(instance.objectId)
mLeakReasonTable[instance.objectId] = "Activity Leak"
MonitorLog.i(OOM_ANALYSIS_TAG,
instance.instanceClassName + " objectId:" + instance.objectId)
}
}
continue
}
//Fragment
if (fragmentHeapClass?.objectId == superId1) { //判断是否是Fragment
val fragmentManager = instance[fragmentHeapClass.name, FRAGMENT_MANAGER_FIELD_NAME] //get Fragment mFragmentManager
if (fragmentManager != null && fragmentManager.value.asObject == null) {
val mCalledField = instance[fragmentHeapClass.name, FRAGMENT_MCALLED_FIELD_NAME]
//mCalled为true且fragment manager为空时认为fragment已经destroy
val isLeak = mCalledField != null && mCalledField.value.asBoolean!!
val objectCounter = updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, isLeak)
MonitorLog.i(TAG, "fragment name:" + instance.instanceClassName + " isLeak:" + isLeak)
if (objectCounter.leakCnt <= SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD && isLeak) {
mLeakingObjectIds.add(instance.objectId)
mLeakReasonTable[instance.objectId] = "Fragment Leak"
MonitorLog.i(OOM_ANALYSIS_TAG,
instance.instanceClassName + " objectId:" + instance.objectId)
}
}
continue
}
//Bitmap
if (bitmapHeapClass?.objectId == superId1) { //bitmap对象
val fieldWidth = instance[BITMAP_CLASS_NAME, "mWidth"]
val fieldHeight = instance[BITMAP_CLASS_NAME, "mHeight"]
val width = fieldWidth!!.value.asInt!!
val height = fieldHeight!!.value.asInt!!
if (width * height >= DEFAULT_BIG_BITMAP) { //大于阀值
val objectCounter = updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, true)
MonitorLog.e(TAG, "suspect leak! bitmap name: ${instance.instanceClassName}" +
" width: ${width} height:${height}")
if (objectCounter.leakCnt <= SAME_CLASS_LEAK_OBJECT_PATH_THRESHOLD) {
mLeakingObjectIds.add(instance.objectId)
mLeakReasonTable[instance.objectId] = "Bitmap Size Over Threshold, ${width}x${height}"
MonitorLog.i(OOM_ANALYSIS_TAG,
instance.instanceClassName + " objectId:" + instance.objectId)
//加入大对象泄露json
val leakObject = HeapReport.LeakObject().apply {
className = instance.instanceClassName
size = (width * height).toString()
extDetail = "$width x $height"
objectId = (instance.objectId and 0xffffffffL).toString()
}
mLeakModel.leakObjects.add(leakObject)
}
}
continue
}
//nativeallocation/NativeAllocationThunk/window
if (nativeAllocationHeapClass?.objectId == superId1
|| nativeAllocationThunkHeapClass?.objectId == superId1
|| windowClass?.objectId == superId1) {
updateClassObjectCounterMap(classObjectCounterMap, instanceClassId, false)
}
}
//关注class和对应instance数量,加入json
for ((instanceId, objectCounter) in classObjectCounterMap) {
val leakClass = HeapReport.ClassInfo().apply {
val heapClass = mHeapGraph.findObjectById(instanceId).asClass
className = heapClass?.name
instanceCount = objectCounter.allCnt.toString()
MonitorLog.i(OOM_ANALYSIS_TAG, "leakClass.className: $className leakClass.objectCount: $instanceCount")
}
mLeakModel.classInfos.add(leakClass)
}
//查找基本类型数组
val primitiveArrayIterator = mHeapGraph.primitiveArrays.iterator()
while (primitiveArrayIterator.hasNext()) {
val primitiveArray = primitiveArrayIterator.next()
val arraySize = primitiveArray.recordSize
if (arraySize >= DEFAULT_BIG_PRIMITIVE_ARRAY) {
val arrayName = primitiveArray.arrayClassName
val typeName = primitiveArray.primitiveType.toString()
MonitorLog.e(OOM_ANALYSIS_TAG,
"uspect leak! primitive arrayName:" + arrayName
+ " size:" + arraySize + " typeName:" + typeName
+ ", objectId:" + (primitiveArray.objectId and 0xffffffffL)
+ ", toString:" + primitiveArray.toString())
mLeakingObjectIds.add(primitiveArray.objectId)
mLeakReasonTable[primitiveArray.objectId] = "Primitive Array Size Over Threshold, ${arraySize}"
val leakObject = HeapReport.LeakObject().apply {
className = arrayName
size = arraySize.toString()
objectId = (primitiveArray.objectId and 0xffffffffL).toString()
}
mLeakModel.leakObjects.add(leakObject)
}
}
//查找对象数组
val objectArrayIterator = mHeapGraph.objectArrays.iterator()
while (objectArrayIterator.hasNext()) {
val objectArray = objectArrayIterator.next()
val arraySize = objectArray.recordSize
if (arraySize >= DEFAULT_BIG_OBJECT_ARRAY) {
val arrayName = objectArray.arrayClassName
MonitorLog.i(OOM_ANALYSIS_TAG,
"object arrayName:" + arrayName + " objectId:" + objectArray.objectId)
mLeakingObjectIds.add(objectArray.objectId)
val leakObject = HeapReport.LeakObject().apply {
className = arrayName
size = arraySize.toString()
objectId = (objectArray.objectId and 0xffffffffL).toString()
}
mLeakModel.leakObjects.add(leakObject)
}
}
val endTime = System.currentTimeMillis()
mLeakModel.runningInfo?.filterInstanceTime = ((endTime - startTime).toFloat() / 1000).toString()
MonitorLog.i(OOM_ANALYSIS_TAG, "filterLeakingObjects time:" + 1.0f * (endTime - startTime) / 1000)
}
使用案例
LeakMaker 这里模拟了几个泄漏,Activity泄漏,Bitmap大对象,Fragment泄漏,基本数据类型数组以及对象数组的大对象,我们执行下,然后分析下生成的Json文件
测试demo会创建大量的线程,触发dump流程
Json字段分析:
classInfos:则是在HeapAnalysisService.filterLeakingObjects遍历镜像的所有instance,收集的信息,每个对象对应的实例个数,这里可以看到Bitmap,LeakedActivity都存在实例
gcPaths:获取泄漏对象的路径,定位问题
leakObjects:大对象泄漏信息,存储Bitmap,基本类型数组,对象数据的泄漏的大小,className等信息
runningInfo:通过Linux文件或者系统Api获取到一些信息,虚拟机堆内存信息,物理内存信息,虚拟内存信息,线程信息,fd信息等,可以获取到当前系统的负载情况
LeakMaker
public static void makeLeak(Context context) {
leakMakerList.add(new ActivityLeakMaker());
leakMakerList.add(new BitmapLeakMaker());
leakMakerList.add(new ByteArrayLeakMaker());
leakMakerList.add(new FragmentLeakMaker());
leakMakerList.add(new StringLeakMaker());
for (LeakMaker leakMaker : leakMakerList) {
leakMaker.startLeak(context);
}
for (int i = 0; i < 700; i++) {
new Thread(() -> {
try {
Thread.sleep(200000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
{
"classInfos":[
{
"className":"libcore.util.NativeAllocationRegistry$CleanerThunk",
"instanceCount":"2386"
},
{
"className":"libcore.util.NativeAllocationRegistry",
"instanceCount":"32"
},
{
"className":"com.android.internal.policy.HwPhoneWindow",
"instanceCount":"3"
},
{
"className":"android.graphics.Bitmap",
"instanceCount":"1"
},
{
"className":"com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
"instanceCount":"1"
}
],
"gcPaths":[
{
"gcRoot":"System class",
"instanceCount":1,
"leakReason":"Primitive Array Size Over Threshold, 431037",
"path":[
{
"declaredClass":"android.icu.impl.coll.CollationRoot",
"reference":"android.icu.impl.coll.CollationRoot.rootSingleton",
"referenceType":"STATIC_FIELD"
},
{
"declaredClass":"android.icu.impl.coll.CollationTailoring",
"reference":"android.icu.impl.coll.CollationTailoring.trie",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"android.icu.impl.Trie2",
"reference":"android.icu.impl.Trie2_32.data32",
"referenceType":"INSTANCE_FIELD"
},
{
"reference":"int[]",
"referenceType":"array"
}
],
"signature":"72c7b40fff431b6442d8de34a1f395379db13b9"
},
{
"gcRoot":"System class",
"instanceCount":1,
"leakReason":"Activity Leak",
"path":[
{
"declaredClass":"android.app.ActivityThread",
"reference":"android.app.ActivityThread.sCurrentActivityThread",
"referenceType":"STATIC_FIELD"
},
{
"declaredClass":"android.app.ActivityThread",
"reference":"android.app.ActivityThread.mActivities",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"android.util.ArrayMap",
"reference":"android.util.ArrayMap.mArray",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"android.app.ActivityThread$ActivityClientRecord",
"reference":"android.app.ActivityThread$ActivityClientRecord.activity",
"referenceType":"INSTANCE_FIELD"
},
{
"reference":"com.kwai.koom.demo.javaleak.test.ActivityLeakMaker$LeakedActivity",
"referenceType":"instance"
}
],
"signature":"e0ceb5289d667a9c86a93c57856e90377d4e4"
},
{
"gcRoot":"Local variable in native code",
"instanceCount":1,
"leakReason":"Bitmap Size Over Threshold, 1920x1080",
"path":[
{
"declaredClass":"java.lang.ClassLoader",
"reference":"dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference":"com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
"referenceType":"STATIC_FIELD"
},
{
"declaredClass":"java.util.ArrayList",
"reference":"java.util.ArrayList.elementData",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference":"com.kwai.koom.demo.javaleak.test.BitmapLeakMaker.uselessObjectList",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.util.ArrayList",
"reference":"java.util.ArrayList.elementData",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"reference":"android.graphics.Bitmap",
"referenceType":"instance"
}
],
"signature":"38ba5ba71b7599737372f965417abcf2765dbb2a"
},
{
"gcRoot":"Local variable in native code",
"instanceCount":1,
"leakReason":"Primitive Array Size Over Threshold, 524301",
"path":[
{
"declaredClass":"java.lang.ClassLoader",
"reference":"dalvik.system.PathClassLoader.runtimeInternalObjects",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference":"com.kwai.koom.demo.javaleak.test.LeakMaker.leakMakerList",
"referenceType":"STATIC_FIELD"
},
{
"declaredClass":"java.util.ArrayList",
"reference":"java.util.ArrayList.elementData",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"com.kwai.koom.demo.javaleak.test.LeakMaker",
"reference":"com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker.uselessObjectList",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.util.ArrayList",
"reference":"java.util.ArrayList.elementData",
"referenceType":"INSTANCE_FIELD"
},
{
"declaredClass":"java.lang.Object[]",
"reference":"java.lang.Object[]",
"referenceType":"ARRAY_ENTRY"
},
{
"declaredClass":"com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker$ByteArrayHolder",
"reference":"com.kwai.koom.demo.javaleak.test.ByteArrayLeakMaker$ByteArrayHolder.array",
"referenceType":"INSTANCE_FIELD"
},
{
"reference":"byte[]",
"referenceType":"array"
}
],
"signature":"19eadfb7e6e6db237f5ab5540f1dfb9cc53c6"
}
],
"leakObjects":[
{
"className":"android.graphics.Bitmap",
"extDetail":"1920 x 1080",
"objectId":"318259112",
"size":"2073600"
},
{
"className":"int[]",
"objectId":"2008600576",
"size":"431037"
},
{
"className":"byte[]",
"objectId":"2009292800",
"size":"524301"
},
{
"className":"byte[]",
"objectId":"2009821184",
"size":"524301"
},
{
"className":"char[]",
"objectId":"2010349568",
"size":"1048589"
},
{
"className":"char[]",
"objectId":"2011402256",
"size":"1048589"
}
],
"runningInfo":{
"buildModel":"JNY-AL10",
"currentPage":"javaleak.JavaLeakTestActivity",
"deviceMemAvaliable":"4127.039",
"deviceMemTotal":"7703.6406",
"dumpReason":"reason_thread_oom",
"fdCount":"120",
"filterInstanceTime":"1.014",
"findGCPathTime":"7.273",
"jvmMax":"384.0",
"jvmUsed":"5.931076",
"manufacture":"HUAWEI",
"nowTime":"2023-07-16_17-12-57_575",
"pss":"81.899414mb",
"rss":"187.40625mb",
"sdkInt":"29",
"threadCount":"228",
"threadList":[
".kwai.koom.demo,Jit thread pool,Signal Catcher,ADB-JDWP Connec,HeapTaskDaemon,ReferenceQueueD,FinalizerDaemon,FinalizerWatchd,Binder:21550_1,Binder:21550_2,Binder:21550_3,AppEyeUiProbeTh,Profile Saver,Binder:21550_4,RenderThread,mali-mem-purge,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-utility-wo,mali-cmar-backe,mali-hist-dump,JDWP Transport ,JDWP Event Help,JDWP Command Re,queued-work-loo,LoopThread,Thread-4,Thread-5,Thread-6,Thread-7,Thread-8,Thread-9,Thread-10,Thread-11,Thread-12,Thread-13,Thread-14,Thread-15,Thread-16,Thread-17,Thread-18,Thread-19,Thread-20,Thread-21,Thread-22,Thread-23,Thread-24,Thread-25,Thread-26,Thread-27,Thread-28,Thread-29,Thread-30,Thread-31,Thread-32,Thread-33,Thread-34,Thread-35,Thread-36,Thread-37,Thread-38,Thread-39,Thread-40,Thread-41,Thread-42,Thread-43,Thread-44,Thread-45,Thread-46,Thread-47,Thread-48,Thread-49,Thread-50,Thread-51,Thread-52,Thread-53,Thread-54,Thread-55,Thread-56,Thread-57,Thread-58,Thread-59,Thread-60,Thread-61,Thread-62,Thread-63,Thread-64,Thread-65,Thread-66,Thread-67,Thread-68,Thread-69,Thread-70,Thread-71,Thread-72,Thread-73,Thread-74,Thread-75,Thread-76,Thread-77,Thread-78,Thread-79,Thread-80,Thread-81,Thread-82,Thread-83,Thread-84,Thread-85,Thread-86,Thread-87,Thread-88,Thread-89,Thread-90,Thread-91,Thread-92,Thread-93,Thread-94,Thread-95,Thread-96,Thread-97,Thread-98,Thread-99,Thread-100,Thread-101,Thread-102,Thread-103,Thread-104,Thread-105,Thread-106,Thread-107,Thread-108,Thread-109,Thread-110,Thread-111,Thread-112,Thread-113,Thread-114,Thread-115,Thread-116,Thread-117,Thread-118,Thread-119,Thread-120,Thread-121,Thread-122,Thread-123,Thread-124,Thread-125,Thread-126,Thread-127,Thread-128,Thread-129,Thread-130,Thread-131,Thread-132,Thread-133,Thread-134,Thread-135,Thread-136,Thread-137,Thread-138,Thread-139,Thread-140,Thread-141,Thread-142,Thread-143,Thread-144,Thread-145,Thread-146,Thread-147,Thread-148,Thread-149,Thread-150,Thread-151,Thread-152,Thread-153,Thread-154,Thread-155,Thread-156,Thread-157,Thread-158,Thread-159,Thread-160,Thread-161,Thread-162,Thread-163,Thread-164,Thread-165,Thread-166,Thread-167,Thread-168,Thread-169,Thread-170,Thread-171,Thread-172,Thread-173,Thread-174,Thread-175,Thread-176,Thread-177,Thread-178,Thread-179,Thread-180,Thread-181,Thread-182,Thread-183,Thread-184,Thread-185,Thread-186,Thread-187,Thread-188,Thread-189,Thread-190,Thread-191,Thread-192,Thread-193,Thread-194,Thread-195,Thread-196,Thread-197,Thread-198,Thread-199,Thread-200,Thread-201,Thread-202,Thread-203,Thread-204,Thread-205,Thread-206,Thread-207,Thread-208,Thread-209,Thread-210,Thread-211,Thread-212,Thread-213,Thread-214,Thread-215,Thread-216,Thread-217,Thread-218,Thread-219,Thread-220,Thread-221,Thread-222,Thread-223,Thread-224,Thread-225,Thread-226,Thread-227,Thread-228,Thread-229,Thread-230,Thread-231,Thread-232,Thread-233,Thread-234,Thread-235,Thread-236,Thread-237,Thread-238,Thread-239,Thread-240,Thread-241,Thread-242,Thread-243,Thread-244,Thread-245,Thread-246,Thread-247,Thread-248,Thread-249,Thread-250,Thread-251,Thread-252,Thread-253"
],
"usageSeconds":"6",
"vss":"9174.836mb"
}
}
到这Koom Java内存的监控分析完了,下一篇会Koom Native内存监控,以及线程泄漏监控