背景知识
Android中每个App默认情况下是运行在一个独立进程中的, 而这个独立进程正是从Zygote孵化出来的VM进程, 也就是说, 也就是说每个Android APP在运行时会启动一个Java虚拟机。并且系统会给它分配固定的内存空间(手机厂商会根据手机的配置情况来对其进行调整)。
一、Android VM的内存空间
Android是一个多任务系统, 为了保证多任务的运行, Android给每个App可使用的Heap大小设定了一个限定值.这个值是系统设置的prop值, 保存在System/build.prop
文件中. 一般国内的手机厂商都会做修改, 根据手机配置不同而不同, 可以直接打开查看与修改。
其中和虚拟机内存相关的主要有以下三个:
dalvik.vm.heapstartsize
– App启动后,系统分配给它的Heap初始大小,随着App使用可增加。
dalvik.vm.heapgrowthlimit
– 默认情况下, App可使用的Heap的最大值, 超过这个值就会产生OOM.
dalvik.vm.heapsize
– 如果App的manifest文件中配置了largeHeap属性, 那么App可使用的Heap的最大值为此项设定值。
1
2
3
4<application
android:largeHeap="true">
...
</application>
所以对于同一个手机,不开启largeHeap
属性时与多进程时,每个APP的虚拟机分配的内存的上限都是heapgrowthlimit
。
1.查看内存的API
Android在ActivityManager
类中提供了API可以运行时获取这些属性值,如下:
1 | //ActivityManager的getMemoryClass()获得内用正常情况下内存的大小,即heapgrowthlimit的值 |
二、Android VM内存分配流程
虚拟机分配内存的具体源码可以AOSP的Heap.cpp文件中查看:
1 | /* Try as hard as possible to allocate some memory. |
具体流程如下:
- 尝试分配,如果成功则返回,失败则转入步骤2
- 判断是否gc正在进行垃圾回收,如果正在进行则等待回收完成之后,尝试分配。如果成功则返回,失败则转入步骤3
- 自己启动gc进行垃圾回收,这里gcForMalloc的参数是false。所以不会回收软引用,回收完成后尝试分配,如果成功则返回,失败则转入步骤4
- 调用dvmHeapSourceAllocAndGrow尝试分配,这个函数会扩张堆的大小,失败转入步骤5
- 进入回收软引用阶段,这里gcForMalloc的参数是ture,所以需要回收软引用。然后再调用dvmHeapSourceAllocAndGrow尝试分配,如果失败则抛出OOM
小结
所以产生OOM时,一定是java的堆中 已有的内存 + 申请的内存 >= heapgrowthlimit
导致的,不会因为手机目前物理内存是否紧张而改变 - 当物理内存非常紧张时系统会通过LowMemory Killer杀掉一些低优先级的进程。
相应的,物理内存非常充足的情况也会有OOM的情况发生。
三、出现OOM的建议解决方案
当APP出现OOM时,建议可以从以下两个方向来处理:
- 排查内存泄露问题
- 排查各个功能是否内存泄露情况,可以通过Android Studio中的MemoryMonitor功能进行分析,Memory Monitor也集成了HPROF Viewer和Allocation Tracker可以分析内存快照与内存分配追踪。另外推荐一个工具,square公司开源的leakcanary,非常简洁好用。
- 排查进程初始化时就直接申请并常驻内存的对象以及其他功能里申请的static对象或者单例对象的必要性。
内存优化
按照谷歌在youtube上发布的性能优化典范之内存篇,优化各功能的内存,或可参照胡凯的总结。大致有以下这些,具体请参见原文:减少对象的内存占用
- 使用更加轻量的数据结构
- 避免在Android里使用enum
- 减少Bitmap对象的内存占用
- 使用更小的图片
内存对象的重复利用
复用系统自带的资源
ListView中对ConvertView的复用
Bitmap对象的复用
避免在ondraw方法里执行对象的创建
StringBuilder代替String
避免对象的内存泄露
- 注意Activity的泄露
- 考虑使用Applicaiton Context代替Activity Context
- 注意临时Bitmap对象的及时回收
- 注意监听器的注销
- 注意缓存容器里的对象泄露
- 注意Webview的泄露
- 注意Cursor对象的及时关闭
内存使用策略的优化
- 谨慎使用large heap
- 综合考虑设备的内存阈值与其他因素设计合适的缓存大小
- onLowMemory与onTrimMemory
- 资源文件需要选择合适的文件夹进行存放
- Try catch某些大内存分配的操作
- 谨慎使用static对象
- 特别留意单例对象中不合理的持有
- 珍惜Services资源
- 优化布局层次,减少内存消耗
- 谨慎使用“抽象”编程
- 使用nano protobufs序列化数据
- 谨慎使用依赖注入框架
- 谨慎使用多进程
- 使用ProGuard来剔除不需要的代码
- 谨慎使用第三方libraries
- 考虑不同的实现方式来优化内存占用