前言
本章主要围绕内存相关的知识点讲解;
内存分配
在内存优化中,我们通常需要借助一些常用的 adb 命令,方便我们快速定位,下面是一些常用的 adb 命令总结
常用 adb 命令
arduino
代码解读
复制代码
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 内存分配模型
java 对象生命周期
- 创建
- 为对象分配内存空间,调用构造方法构造对象
- 应用
- 此时对象至少被一个强引用持有
- 不可见
- 对象还存在,但是没有被强引用了,如果被GC扫描到了,就会进行可达性分析
- 不可达
- 可达性分析,发现不可达(也就是没有任何强应用了)
- 收集
- GC准备对该对象内存空间进行重新分配
- 如果重写了finalize方法,这个方法就会被调用
- 终结
- 被垃圾回收器 回收
- 对象空间重新分配
- 对象被回收之后,这块空间重新分配
这边整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处即可】免费获取
- 对象被回收之后,这块空间重新分配
java 对象的内存布局
- 对象头
- 存储对象自身的运行时数据
- 哈希码
- GC 分代年龄
- 锁状态标识
- 线程持有的锁
- 偏向线程 ID
- 偏向时间戳
- 类型指针
- 若为对象数据,还应该记录数组长度的数据
- 存储对象自身的运行时数据
- 实例数据
- 对齐填充
本地方法栈
线程私有,每个线程不一样
程序计数器
线程切换使用,也是线程私有;
垃圾回收算法
标记清除
位置不连续,产生内存碎片,效率略低,两遍扫描。多次进行标记清除算法,内存就会千疮百孔;先标记存活对象,然后清除回收对象,产生内存碎片;
复制算法
实现简单,运行高效,没有内存碎片,但是利用率只有一半;将内存一分为二,将存活对象复制到剩余的一半,其余的回收
标记整理算法
没有内存碎片,效率偏低,两边扫描、指针需要调整;先标记,然后将存活对象整理到一起。剩下的回收掉
分代收集算法
综合应用上面的算法;
四种引用
Android 内存回收机制![](https://i-blog.csdnimg.cn/direct/a38be43208d045fa94b7ffe45758837b.png)
Android Kill 机制![](https://i-blog.csdnimg.cn/direct/da07ef98c0a64c249c0f77b4542b9cdb.png)
- 在 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 注册
ini
代码解读
复制代码
<application> <provider android:name="leakcanary.internal.AppWatcherInstaller$MainProcess" android:authorities="${applicationId}.leakcanary-installer" android:exported="false"/> </application>
这是通过注册 provider 进行 Leakcanary 的初始化逻辑,我们进入 AppWatcherInstaller 中看下
kotlin
代码解读
复制代码
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,我们进入这个方法看下:
kotlin
代码解读
复制代码
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 是如何被监视的;
kotlin
代码解读
复制代码
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 是如何被监视的;
scss
代码解读
复制代码
@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 是怎么删除的;
csharp
代码解读
复制代码
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 任务是如何创建的;
kotlin
代码解读
复制代码
val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5) private val checkRetainedExecutor = Executor { //在主线程延时五秒执行任务 mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis) }
我们接着看下无法回收的 Activity 是如何获取的,进入 moveToRetained 方法看下;
kotlin
代码解读
复制代码
@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 方法看下;
kotlin
代码解读
复制代码
fun onObjectRetained() { // 再次检查内存是否泄露 scheduleRetainedObjectCheck("found new object retained") }
我们进入 scheduleRetainedObjectCheck 方法看下;
kotlin
代码解读
复制代码
private fun scheduleRetainedObjectCheck( reason: String, delayMillis: Long ) { if (checkScheduled) { return } checkScheduled = true backgroundHandler.postDelayed({ checkScheduled = false // 检查泄漏对象 checkRetainedObjects(reason) }, delayMillis) }
我们进入 checkRetainedObjects 方法看下;
scss
代码解读
复制代码
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) }
我们来看下当前所有存在对象的个数是如何检查的;
kotlin
代码解读
复制代码
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 有没有泄露;
好了,内存这块就讲到这里吧
下一章预告
继续搞内存
欢迎三连
来到来了,点个关注,点个赞吧,你的支持是我最大的动力~