android 设备对内存的限制
- adb shell cat /system/build.prop --需要先指定虚拟机目录
dalvik.vm.heapstartsize=16m --app启动的初始分配内存
dalvik.vm.heapgrowthlimit = xxm --app最大内存限制
dalvik.vm.heapsize=xxm --开启largeHeap="true"的最大限制 - 代码获取
(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE).getMemoryClass(); - 手机厂商可以在 \frameworks\base\core\jni\AndroidRuntime.cpp 中 startVm里面修改
内存指标
一般用PSS :USS(进程独占的内存)+按比例包含共享内存
内存分配与回收机制 (GC 分代,对象头里面)
- 创建对象后在Young Generation-Eden区
- 执行GC后,如果对象存货,则复制到Young Generation-Survivor0区
- Young Generation-Survivor0区满时,该区存货对象复制到Young Generation-Survivor1区,然后清空Survivor0,再角色互换(复制回收算法)
- 当第3步骤到达一定次数后,存活对象被复制到old Generation 区
- 当这个对象在old generation区停留到达一定程度,会被移动到Perment Generation区。
综述:Young Generation对象存活时间段,复制回收算法;Old Generation 对象存活时间长,标记回收算法
java对象的生命周期
Created--In use--Invisible--Unreachable--Collected--Finalized--Deallocated
可达性分析
可回收:没有被引用,或者自身循环 引用的会被 标记为可回收;
不可回收(GC roots):静态变量、线程栈变量、常量池、jni指针
java引用
强引用
软引用 SoftReference:触发GC后会根据 内存情况回收
弱引用 WeakReference: 触发GC 就会回收,ViewModel接口用
虚应用 PhantomReference:只是判断GC是否工作用
Reference Queue:reference对象所引用的对象被GC回收时,该reference对象将会被加入引用队列中。
算法
- 标记清除算法:位置不连续,产生碎片;效率略低;两遍扫描
- 复制算法:实现简单,运行高效;没有内存碎片;利用率只有一半 yang s generation
- 标记整理算法:没有内存碎片;效率偏低;两遍扫描、指针需要调整;old generation
app内存限制
- java层:vm heap;超过则内存溢出异常
- native层:native process限制
AMS oom_adj 看杀进程机制
值越大越容易被杀
内存问题
内存抖动
内存图呈现波形或者锯齿、GC导致卡顿。内存泄露
不再使用的对象被GC roots引用,导致不能回收,实际可用内存变小内存溢出 oom
oom会导致程序异常;app超过安卓设备厂商设置的最大内存分配就会OOM
oom分类:java堆内存溢出;无足够连续内存空间;FD数量超出限制;线程数量超出限制;虚拟内存不足。
常用内存分析命令
常用
- dumpsys meninfo
查看进程的oom adj ;某个进程的内存情况
程序在A页面执行此命令后再操作及进入B页面再回来看下ViewRootImpl、Activities、Views
不常用
- procrank
详细的 PSS、USS等指标,没用 - cat /proc/meminfo
看下可用内存 少用 - free?
查看系统可用内存 - showmap
虚拟地址区域内存,没用 - vmstat
查看内存、进程运行队列、系统切换cpu时间占比等
常用分析工具
mat:一般用 柱状图分析
profiler里面导出的hprof要用hprof-conv命令转换才能用(androidsdk自带)
incoming references:对象被外部引用(含自己)
outgoing references:对象引用了外部对象(含自己)
Shallow Heap:自己
Retained Heap:自己+所有引用;释放后会对应释放没被引用的memory profiler
leakcanary
内存泄露常见场景和解决方案
- 资源对象未关闭:比如bitmap用后要关闭
- 注册对象未注销:BroadCastReceiver 用完及时注销
- 类的静态变量持有大数据:避免静态变量持有大数据,使用数据库或sharepreference
- 单例造成内存泄露:优先使用Application的Context;如需要使用Activity的Context;使用弱引用方式,如果获取不到,return.
- 非静态内部类的静态实例:该实例的生命周期和应用一样长,导致该静态实例一直持有Activity的引用(Activity不能正常回收);我们可以将内部类抽取出来封装成一个单例;Context处理同上一条,用完置空方便GC
- Handler临时性内存泄露:Handler持有Activity 或者Activity退出时消息队列中还有未处理的消息会造成oom;使用静态Handler内部类,对持有的对象弱引用(一般Activity);在Activity Destory或Stop时,移出消息队列中的消息。
- 容器中的对象没有清理造成的内存泄露:程序退出之前,将集合里面的东西clear,并置为null
- webview:应用中使用一次,内存就不会释放;需要为webview开启一个独立的进测,使用aidl与主进程通信,webview的进程根据业务需要在合适的时机销毁
- 使用LIstView 造成oom:使用Adapter时,使用缓存的convertView
LeakCanary
- 引用
debugImplementation('com.squareup.leakcanary:leakcanary-android:2.2')
implementation 'com.squareup.leakcanary:leakcanary-object-watcher-android:2.2'
使用
运行后 有问题的会有消息;直接查看消息即可;也可以把hprof文件在mat里面分析原理
- contentProvider onCreate--(LeakSentryinstaller)InternallLeakSentry.install(application) 初始化:AMS-handleBindApplication中看到installContentProviders(app,data.providers)在callApplicationOnCreate(app)前执行
- interface ActivityLifecycleCallbacks 监听 onActivityDestroyed
- refWatcher.watch(activity):观察的对象放入watch
- removeWeaklyReachableReferences根据ReferenceQueue中是否包含观察对象的引用来判断释放
- 创建一个监控对象的弱引用
- 与ReferenceQueue关联
- 生成唯一的key标识
- 存放到map容器
- 5秒后检测
- 如果对象失去强引用 ,就进入ReferenceQueue
- 移出容器中的key引用
- 如果监控的对象还在watchedReferences
- 加入retainedReference
- haha 确诊检测内存泄露:可达性分析
ComponentCallbacks2
- onLowMemory:内存低的时候处理
- onTrimMemory:内存要不够用的时候处理
内存优化思想
- 设备分级
- bitmap优化
- 这个解决了可以解决百分之八九十的内存泄露问题
- 统一图片库,都用glide,谷歌推荐的处理方式处理的
- 线上线下监控 hook