本篇文章,将以一个实际案例为基础,从分析问题表象出发,在借助Profiler的情况下,教你如何一步一步剖析和解决问题。
最近测试那边反馈了一个系统卡顿的问题。
出现的场景,是在安卓设备(手机)运行一段时间后,会变得很卡顿。
系统出现卡顿,我们一般会从2个角度,切入分析:
通过adb shell top
命令,我们可以看到当前cpu占用率,以及每个进程的占用率,如下图所示:
这里面总的可用CPU可用率为800%,user进程占用100%,system进程占用291%,空闲有391%。相当于,CPU空闲率几乎接近50%。由此,可以排除CPU占用率高的问题。
adb shell dumpsys meminfo
命令,我们可以查看各个进程对内存的占用率,以及剩余可用的内存空间,如下图所示:这里的图1、图2,只是示例图,非当时案发现场的内存占用率。当时测试人员,测出系统卡顿时,中心服务的进程内存占用高达1.4+GB,Free RAM,所剩无几。由此,可以初步判断,是中心服务进程内存占用过高,拖慢了整个系统,导致系统卡顿。
基于分析一的情况,接下来,我们借助Android Studio的Profiler工具对中心服务进程的内存占用,进行监控,如下图所示:
这里我放着煲机,煲了有十几分钟,通过图片,我们可以明显发现,随着时间的推移,Native层的内存占用率越来越高。
由此,我们可以进一步猜测,是底层代码没有释放相关的内存资源。此时,我们可以通过左边控制面板,通过选中Record nativ allocations
,并点击Record
按钮,进行内存分配的监控。监控时长,视内存飙升的速度而定,飙升的越高,监控时长可以越短。监控完,点击stop
即可。
这里以60s
为例,识别结果如下图所示:
通过图片的调用栈关系,我们可以看出是由中心服务进程中的语音模块,有个ZoneRecorder类,该类的runAudioRecorder方法,调用了jni层native-lib.cpp的代码。
最终触发,这个底层方法:JNIEnv::GetByteArrayElements(),这个方法在不断的吃内存。
随着监控时间变长,这个方法所占用的内存也会变大。
由此,我们已经找出了内存泄露的地方,通过注释掉ZoneRecorder.runAudioRecorder方法的实现,屏蔽掉JNI层的调用后,再重新运行中心服务,可以发现,native层的内存占用,会一直维持在一个较低且非常稳定的范围值之内。
总结,当出一个问题时,我们不能简单的惑于表面,就下判断。比如,出现系统卡顿时,不能第一时间就下定论说是系统的问题,然后就丢给系统人员。
应该抱持科学严谨的态度,对内存分配进行层层追踪,以事实为依据,下的定论,才能更让人信服。