前言
本章主要围绕内存相关的知识点讲解;
内存分配
在内存优化中,我们通常需要借助一些常用的 adb 命令,方便我们快速定位,下面是一些常用的 adb 命令总结
常用 adb 命令
adb shell getprop ro.product.model // 手机型号
adb shell dumpsys battery //电池信息
adb shell wm size //屏幕尺寸
adb shell wm density //屏幕像素密度
adb shell dumpsys window displays // 查看总得设备信息,包含了显示屏编号,像素密度,分辨率等等
adb shell settings get secure android_id // android id 一般用来标识不同的手机
adb shell service call iphonesubinfo 1 // IMEI
adb shell getprop ro.build.version.release // 版本
adb shell cat /proc/cpuinfo // cpu 信息
adb shell cat /system/build.prop // 查看构建文件信息
adb shell cat /proc/meminfo // 查看内存信息
adb shell dumpsys meminfo //获取内存信息
adb shell procrank //查看进程内存排名
adb shell top
adb shell top |grep your app name
adb shell vmstat //
adb shell vmstat 2 //
adb shell top -d 20 > meminfo
内存指标概念
RSS(共享库、so 动态链接库)
USS 进程独占内存空间
procs(进程)
r: Running队列中进程数量
b: IO wait的进程数量
memory(内存)
free: 可用内存大小
mapped:mmap映射的内存大小
anon: 匿名内存大小
slab: slab的内存大小
system(系统)
in: 每秒的中断次数(包括时钟中断)
cs: 每秒上下文切换的次数
cpu(处理器)
us: user time (用户时间)
ni: nice time
sy: system time(系统时间)
id: idle time
wa: iowait time
ir: interrupt time
用户时间 + 系统时间 = 进程时间;
内存分配
Java 内存分配模型
内存分配模型,详细的讲解可以看我之前的文章(https://juejin.cn/post/7314365534107238438)
java 对象生命周期
- 创建
- 为对象分配内存空间,调用构造方法构造对象
- 应用
- 此时对象至少被一个强引用持有
- 不可见
- 对象还存在,但是没有被强引用了,如果被GC扫描到了,就会进行可达性分析
- 不可达
- 可达性分析,发现不可达(也就是没有任何强应用了)
- 收集
- GC准备对该对象内存空间进行重新分配
- 如果重写了finalize方法,这个方法就会被调用
- 终结
- 被垃圾回收器 回收
- 对象空间重新分配
- 对象被回收之后,这块空间重新分配
java 对象的内存布局
- 对象头
- 存储对象自身的运行时数据
- 哈希码
- GC 分代年龄
- 锁状态标识
- 线程持有的锁
- 偏向线程 ID
- 偏向时间戳
- 类型指针
- 若为对象数据,还应该记录数组长度的数据
- 存储对象自身的运行时数据
- 实例数据
- 对齐填充
本地方法栈
线程私有,每个线程不一样
程序计数器
线程切换使用,也是线程私有;
垃圾回收算法
标记清除
位置不连续,产生内存碎片,效率略低,两遍扫描。多次进行标记清除算法,内存就会千疮百孔;先标记存活对象,然后清除回收对象,产生内存碎片;
复制算法
实现简单,运行高效,没有内存碎片,但是利用率只有一半;将内存一分为二,将存活对象复制到剩余的一半,其余的回收
标记整理算法
没有内存碎片,效率偏低,两边扫描、指针需要调整;先标记,然后将存活对象整理到一起。剩下的回收掉
分代收集算法
综合应用上面的算法;
四种引用
Android 内存回收机制
Android Kill 机制
- 在 Android 的 lowmemroykiller 机制中,会对于所有进程进行分类,对于每一类别的进程会有其 oom_adj 值的取值范围,oom_adj值越高则代表进程越不重要,在系统执行低杀操作时,会从 oom_adj 值越高的开始杀;
- 对于期望较长时间留在后台的服务,应该将服务运行在单独的进程里,即是 UI 进程与 Servie 进程分离,这样期望长时间留在后台的 Serivce 会存在与一个被 lowmemorykiller 分类为 Service 进程的服务而获得较小的Adj 值,而占有大量内存的UI进程则会分类为 Cached 进程,能够在需要的时候更快地被回收;
oom_adj
- 当所有应用退后台之后并不会立马被杀掉,而是通过 oom_adj 进行了一个分级[-16, 15] 从 -16 到 15 的这样的一个区间,应用对应的数字越小 越不容易被 OOM Killer 杀到
- ams 又对齐进行了一个更高等级的区分 oom_score_adj,将应用优先级的区间划分为[-1000, 1000],从 -1000 到 1000,score 的值越低越不容易被杀掉;
- 如果两个应用的 oom_adj 值一样,那么哪个 app 占用内存多,哪个就优先被杀掉。所以要尽可能的降低应用进入后台后的内存,才能保证尽可能的不被系统杀掉;
Android App内存组成以及限制
Android 给每一个 App 都分配一个 VM,让 App 运行在 Dalvik 上,这样即使 App 崩溃了也不会影响到系统,系统给 VM 分配了一定的内存大小,App 可以申请使用的内存大小不会超过此硬性逻辑限制,就算物理内存富余,如果应用超出 VM 最大内存,就会出现内存溢出 crash;
由程序控制操作的内存空间在 heap 上,分为 java heapsize 和 native heapsize;
native 层内存申请不受其限制,native 层受 native process 对内存大小的限制;
修改系统为每个App分配的内存大小;默认为每个app分配 16m 的内存大小;
LeakCanary 源码解析
从垃圾回收机制、源码分析、优缺点中分析;
总结的详细流程如下:
- 通过 provider 进行 Leakcanary 的初始化逻辑
- provider 的 onCreate 方法中,获取 Application
- 加载 Leakcanary
- 检查当前线程是否在主线程
- 检查是否已经加载了 Leakcanary,如果加载了,则直接返回
- 创建 Activity 的监视
- 创建 Activity 的 onDestory 回调监视
- 将可达的 Activity 删除
- 根据 Activity 创建对应的弱引用,并绑定 ReferenceQueue
- 将 reference 保存到 watchedObjects 数组中
- 启动延时 5s 任务
- 获取 GC 无法回收的 Activity
- 通知内存泄露
- 同 Application 绑定
- 创建 Activity 的 onDestory 回调监视
- 创建 Fragment 的监视
- 调用上层模块 InternalLeakCanary.invoke
本质就是:监听 Activity 的 onDestory 方法回调,过 5s 之后进行一次 GC,通过 WeakReference 引用链看它有没有销毁;
LK 中使用的 垃圾回收算法
可达性分析算法、引用计算算法;
可以作为 GC Root 的对象有哪些?
- 在线程栈中的局部变量(即正在被调用的方法里面的参数和局部变量)
- 存活的线程对象
- JNI的引用
- Class对象(在Android中Class被加载后是不会被卸载的)
- 引用类型的静态变量
模块层级(LK都有哪些模块,以及对应的模块都是做什么的?)
leakcanary-android
集成入口模块,提供 LeakCanary 安装,公开 API 等能力
leakcanary-android-core
核心模块
lealcanary-object-watcher、lealcanary-object-watcher-android、lealcanary-object-watcher-android-androidx、lealcanary-object-watcher-android-support-fragment
对象实例观察模块,在 Activity,Fragment 等对象的生命周期中,注册对指定对象实例的观察,有 Activity,Fragment,Fragment View,ViewModel 等;
leakcanary-android-process
和 leakcanary-android 一样,区别是会在单独的进程进行分析;
shark
hprof 文件解析与分析的入口模块;
shark-android
提供特定于 Android 平台的分析能力。例如设备的信息,Android 版本,已知的内存泄露问题等;
shark-graph
分析堆中对象的关系图模块;
shark-hprof
解析 hprof 文件模块;
share-log
日志模块;
LeakCanary 注册
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
</application>
这是通过注册 provider 进行 Leakcanary 的初始化逻辑,我们进入 AppWatcherInstaller 中看下
internal sealed class AppWatcherInstaller : ContentProvider() {
internal class LeakCanaryProcess : AppWatcherInstaller() {
override fun onCreate(): Boolean {
super.onCreate()
AppWatcher.config = AppWatcher.config.copy(enabled = false)
return true
}
}
override fun onCreate(): Boolean {
// 获取 Application
val application = context!!.applicationContext as Application
// 加载 LeakCanary
InternalAppWatcher.install(application)
return true
}
}
加载 Leakcanary,我们进入这个方法看下:
internal object InternalAppWatcher {
// 加载
fun install(application: Application) {
// 检查当前线程是否在主线程
checkMainThread()
if (this::application.isInitialized) {
// 如果 Leakcanary 已经加载过了,直接返回
return
}
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config }
// 监视 Activity
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
// 监视 Fragment
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
// 调用上层模块InternalLeakCanary.invoke
onAppWatcherInstalled(application)
}
}
我们进入 ActivityDestroyWatcher 看下 Activity 是如何被监视的;
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
// 创建 Activity 的 destory 监听回调
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// Activity 的 onDestory 回调,触发存在对象检查
if (configProvider().watchActivities) {
// 通过 objectWatcher 监视 Activity
objectWatcher.watch(activity)
}
}
}
companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
// 创建 Activity 的 onDestory 监听回调
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
// 绑定 Application application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}
我们进入 ObjectWatcher 中看下 Acivity 是如何被监视的;
@Synchronized fun watch(
watchedObject: Any,
name: String
) {
if (!isEnabled()) {
return
}
// 将可达的 activity 删除
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 根据 activity 创建对应的弱引用,并绑定ReferenceQueue
val reference =
KeyedWeakReference(watchedObject, key, name, watchUptimeMillis, queue)
// 将 reference 保存到 watchedObjects 数组中
watchedObjects[key] = reference
// 启动延时 5s 任务
checkRetainedExecutor.execute {
// 获取 GC 无法回收的 Activity
moveToRetained(key)
}
}
我们进入这个 removeWeaklyReachableObjects 方法看下,可达的 Activity 是怎么删除的;
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
// 重点,在 GC 或者 finalization 之前,在 WeakReferences 的被引用对象(这里是Activity)的可达性更改时,会把 WeakReferences 添加到创建时候指定的 ReferenceQueue 队列,这些可达性变更得对象,就是内存不泄露对象
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// 在 watchedObjects 中删除不发送内存泄漏对象,剩下内存泄漏对象;
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
我们接着看下这个延迟 5s 任务是如何创建的;
val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5)
private val checkRetainedExecutor = Executor {
//在主线程延时五秒执行任务
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
我们接着看下无法回收的 Activity 是如何获取的,进入 moveToRetained 方法看下;
@Synchronized private fun moveToRetained(key: String) {
// 将可达activity删除
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
// 保存当前时间作为泄漏时间
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 通知 InternalLeakCanary 发生内存泄漏
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
我们来看下内存泄露的场景是如何进行上报的,我们进入 HeapDumpTrigger 的 onObjectRetained 方法看下;
fun onObjectRetained() {
// 再次检查内存是否泄露
scheduleRetainedObjectCheck("found new object retained")
}
我们进入 scheduleRetainedObjectCheck 方法看下;
private fun scheduleRetainedObjectCheck(
reason: String,
delayMillis: Long
) {
if (checkScheduled) {
return
}
checkScheduled = true
backgroundHandler.postDelayed({
checkScheduled = false
// 检查泄漏对象
checkRetainedObjects(reason)
}, delayMillis)
}
我们进入 checkRetainedObjects 方法看下;
private fun checkRetainedObjects(reason: String) {
...
if (retainedReferenceCount > 0) {
// 执行一次 GC,确认所有存在对象都是泄露对象;
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 检查当前所有存在对象的个数
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
showRetainedCountWithDebuggerAttached(retainedReferenceCount)
// 如果配置了 debug 不使用 heap 且正在 debug,延时 20s 在执行checkRetainedObjects(reason)
scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
return
}
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
dismissRetainedCountNotification()
// 执行dump Heap操作
val heapDumpFile = heapDumper.dumpHeap()
lastDisplayedRetainedObjectCount = 0
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
我们来看下当前所有存在对象的个数是如何检查的;
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int
): Boolean {
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayedRetainedObjectCount = retainedKeysCount
if (retainedKeysCount == 0) {
// 存在对象为 0
return true
}
if (retainedKeysCount < retainedVisibleThreshold) {
// 存在对象低于阈值5个
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
showRetainedCountBelowThresholdNotification(retainedKeysCount, retainedVisibleThreshold)
// 当前应用可见,或者不可见时间间隔少于 5s,重新安排到 2s 后执行 checkRetainedObjects
scheduleRetainedObjectCheck(
"Showing retained objects notification", WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
return false
}
到此,Leakcanary 的流程就整体跑完了;
线上内存如何监控
在引入任何自动分析工具之前,对于 Activity 泄漏,一般都是在自动化测试阶段监控内存占用,一旦超过预期,则发起一次 GC 后进行 Dump Hprof 操作。分析人员将 Hprof 文件导入 MAT 中查看各个 Activity 的引用链,找出被静态成员或 Application 对象等长生命周期对象持有的 Activity,再进行修复。对于冗余的 Bitmap,也是将 Hprof 导入 Android Monitor 后通过 Android Monitor 自带的工具找出冗余的 Bitmap 对象。
自动化监测目标流程
- 自动且较为准确地监测 Activity 泄漏,发现泄漏之后再触发 Dump Hprof 而不是根据预先设定的内存占用阈值盲目触发;
- 自动获取泄漏的 Activity 和冗余 Bitmap 对象的引用链;
- 能灵活地扩展 Hprof 的分析逻辑,必要时允许提取 Hprof 文件人工分析;
监测阶段
Activity 对象被生命周期更长的对象通过强引用持有,使 Activity 生命周期结束后仍无法被 GC 机制回收,导致其占用的内存空间无法得到释放
具体如何做?
- Activity 在执行销毁的时候 我们如何得知?
- 如何判断一个 Activity 无法被 GC 机制回收?
采用 ActivityLifeCycleCallbacks + WeakHashMap 的方式,我们可以不主动暴露 RefrenceQueue 这个对象,WeakHashMap 的 key 可以自动被弱引用,可以自动被回收,那么这个 key 就可以是 Activity,key 被回收了,那么 value 也就跟着被移除了,监听 Activity 的回收,也就达到监听泄露的目的了;
借助 ActivityLifeCycleCallbacks 的 onActivityDestoryed 回调,在回调中将传递过来的 Activity 放入 WeakHashMap 中,然后在 onStop 的回调中通过 GC 监测 Activity 有没有泄露;
好了,内存这块就讲到这里吧
下一章预告
继续搞内存
欢迎三连
来到来了,点个关注,点个赞吧,你的支持是我最大的动力~