Android内存管理(一)

声明:大部分内容为从其他文章中摘录感兴趣的部分,只为记录给自己看。

Stack由操作系统控制,其中主要存储函数地址、函数参数、局部变量等等,所以Stack空间不需要很大,一般为几MB大小。

Heap空间由程序控制,程序员可以使用malloc、new、free、delete等函数调用来操作这片地址空间。Heap为程序完成各种复杂任务提供内存空间,所以空间比较大,一般为几百MB到几GB。

进程的内存空间只是虚拟内存(或者叫做逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。

Android中的进程
(1)native进程:采用C/C++实现,不包含dalvik实例的进程,/system/bin/目录下的程序文件运行后都是以native进程形式存在的。
(2)java进程:Android中运行于dalvik虚拟机之上的进程。dalvik虚拟机的宿主进程由fork()系统调用创建,所以每一个Java进程都是存在于一个native进程中。因此,java进程的内存分配比native进程复杂,因为进程中存在一个虚拟机实例。

Android的java程序为什么容易出现OOM?这个是因为Android系统对dalvik的vm heapsize做了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常(这个阈值可以是48M、24M、16M等,视机型而定),可以通过adb shell getprop | grep dalvik.vm.heapsize查看此值。

这样的设计似乎有些不合理,但Google的目的是为了让Android系统能同时让比较多的进程常驻内存,这样程序启动时就不用每次都重新加载到内存,能够给用户更快的响应。

如果RAM真的不足,这时Android的memory killer会起作用,当RAM所剩不多时,memory killer会杀死一些优先级比较低的进程来释放物理内存,让高优先级程序得到更多的内存。

可以使用adb shell cat /proc/meminfo查看RAM使用情况。
使用adb shell dumpsys meminfo + packagename/pid查看进程的内存信息。

对于一些大型的应用程序(如游戏),内存使用会比较多,很容易超出vm heapsize的限制,这时怎么保证程序不会因为OOM而崩溃呢?

  • 创建子进程
    创建一个新的进程,那么我们就可以把一些对象分配到新进程的heap上了。当然,创建子进程会增加系统开销,而且并不是所有应用都适合这样做。创建子进程的方法:使用android:process标签。
  • 使用jni在native heap上申请空间(推荐)
    nativeheap的增长并不受dalvik vm heapsize的限制。
  • 使用显存
    使用OpenGL textures等API,texture memory不受dalvik vm heapsize限制,比如Android中的GraphicBufferAllocator申请的内存就是显存。

Android中常见的内存泄漏
1、单例(主要原因是因为一般情况下单例都是全局的,有时候会引用一些实际生命周期比较短的变量,导致无法释放)
2、静态变量(同样也是因为生命周期比较长)
3、Handler内存泄漏
4、匿名内部类(匿名内部类会引用外部类,导致无法释放,比如各种回调)
5、资源使用完未关闭(BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap)

对于Android内存泄漏,LeakCanary是最知名的优秀组件。其原理是监控每个activity,在activity onDestroy后,在后台线程检测引用,然后过一段时间进行gc,gc后如果引用还在,那么dump出内存堆栈,并解析进行可视化显示。

但有时候APP本身就是有一些比较耗内存的功能,比如直播、视频播放、音乐播放,我们还能做什么可以降低内存使用,减少OOM呢?

分辨率适配问题

很多情况下图片所占的内存在整个App内存占用中会占大部分。我们知道可以通过将图片放到hdpi/xhdpi/xxhdpi等不同文件夹进行适配,通过xml android:background设置背景图片,或者通过BitmapFactory.decodeResource()方法,图片实际上默认情况下是会进行缩放的。在java层实际调用的函数都是通过BitmapFactory里的decodeResourceStream函数。

decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDPI的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大。

尽管现在已经有比较先进的图片加载组件类似Glide,Fresco等,但是有时就是需要手动拿到一个bitmap或者drawable,特别是在一些可能会频繁调用的场景(如ListView的getView),怎样尽可能对bitmap进行复用呢?这里可以简单自己用WeakReference做一个bitmap缓存池,也可以用类似图片加载库写一个统一的bitmap缓存池,可以参考GlideBitmapPool的实现。

图片压缩

BitmapFactory在解码图片时,可以带一个Options,有一些比较有用的功能,比如:

  • inTargetDensity 表示要被画出来时的目标像素密度
  • inSampleSize 当它小于1时,会被当做1处理,大于1会按比例(1 / inSampleSize)缩小bitmap的宽和高、降低分辨率,大于1时这个值将会被处置为2的倍数。
  • inJustDecodeBounds 字面意思就可以理解为只解析图片的边界,有时如果只是为了获取图片的大小就可以用这个,而不必直接加载整张图片。
  • inPurgeable和inInputShareable 这两个需要一起使用,BitmapFactory的源码里有注释,大致意思是表示在系统内存不足时是否可以回收这个bitmap,有点类似软引用,但实际在5.0以后这两个属性已经被忽略,因为系统认为回收后再解码实际会反而可能导致性能问题。
  • inBitmap 官方推荐使用的参数,表示重复利用图片内存,减少内存分配,在4.4以前只有相同大小的图片内存区域可以复用,4.4以后只要原有的图片比将要解码的图片大即可复用了。

缓存池大小

现在很多图片加载组件都不仅仅使用软引用或者弱引用了,实际上类似Glide默认使用的是LruCache,因为软引用、弱引用都比较难以控制,使用LruCache可以实现比较精细的控制,而默认缓存池设置太大了会导致浪费内存,设置小了又会导致图片经常被回收,所以需要根据每个App的情况,以及设备的分辨率,内存计算出一个比较合理的初始值,可以参考Glide的做法。

内存抖动

Android里内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时还会导致OOM。一个经典的案例是String拼接创建大量小的对象。

而内存抖动为什么会引起OOM呢?主要原因还是因为大量小的对象频繁创建,导致内存碎片,从而当需要分配内存时,虽然总体上还是有剩余内存可分配,而由于这些内存不连续,导致无法分配,系统就直接OOM了。

其他

常用数据结构优化,ArrayMap及SparseArray是Android的系统API,是专门为移动设备而定制的。用于在一定情况下取代HashMap而达到节省内存的目的,具体性能见HashMap、ArrayMap、SparseArray源码分析及性能对比。对于key为int的HashMap尽量使用SparseArray替代,大概可以省30%的内存,而对于其他类型,ArrayMap对内存的节省实际并不明显,10%左右,但是数据量在1000以上时,查找速度可能会变慢。

枚举,Android平台枚举是比较争议的,在较早的Android版本,使用枚举会导致包过大。随着虚拟机的优化,目前枚举变量在Android平台性能问题已经不大,而目前Android官方建议,使用枚举变量还是需要谨慎,因为枚举变量可能比直接用int多使用2倍的内存。

ListView复用,getView里尽量复用convertView,同时因为getView会频繁调用,要避免频繁地生成对象。

谨慎使用多进程,现在很多APP都不是单进程,为了保活,或者提高稳定性都会进行一些进程拆分,而实际上即使是空进程也会占用内存(1M左右),对于使用完的进程,服务都要及时进行回收。

尽量使用系统资源,系统组件,图片甚至控件的id。

减少view的层级,对于可以延迟初始化的页面,使用viewstub。

数据相关:序列化数据使用protobuf可以比xml省30%内存,慎用sharedpreference,因为对于同一个sp,会将整个xml文件载入内存,有时候为了读一个配置,就会将几百k的数据读进内存。数据库字段尽量精简,只提取所需字段。

dex优化,代码优化,谨慎使用外部库。有人觉得代码多少于内存没有关系,实际会有那么点关系,现在稍微大一点的项目动辄就是百万行代码以上,多dex也是常态,不仅占用rom空间,实际上运行的时候需要家长dex也是会占用内存的(几M),有时候为了使用一些库里的某个功能函数就引入了整个庞大的库,此时可以考虑抽取必要的部分,开启proguard优化代码,使用Facebook redex优化dex(好像有不少坑)。

当我们接到一个内存优化任务时,应该从何开始?

1、首先是解决大部分内存泄漏,接入LeakCanary
2、通过MAT查看内存占用,优化占用内存较大的地方
3、对RDM上的OOM进行分析
4、同时对一些逻辑代码进行调整,如APP主页的tab进行数据延迟加载和定时回收

总结,我们可以通过各种内存泄漏检测组件,MAT查看内存占用,Memory Monitor跟踪整个APP的内存变化情况,Heap Viewer查看当前内存快照,Allocation Tracker追踪内存对象的来源,以及利用崩溃上报平台从多方面对App内存进行监控和优化。

参考

【腾讯Bugly干货分享】Android内存优化总结&实践

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,757评论 25 707
  • HereAndroid的内存优化是性能优化中很重要的一部分,而避免OOM又是内存优化中比较核心的一点。这是一篇关于...
    HarryXR阅读 3,805评论 1 24
  • 做事情 有计划 昨天晚上收到小R妈妈发给我的微信,说到:“王老师好,我是小R的妈妈,真不好意思打扰你,上一天班很累...
    王方媛阅读 804评论 0 7
  • 1.Android的控件架构: 每个Activity包含一个Window对象(Window是abstract的,一...
    jacky123阅读 274评论 0 1
  • 如果有人想开发个产品,第一步首先要作什么,随便找个创业者,估计都能答上来,“最简可行产品”嘛。它指的是“一个这样的...
    Acetx阅读 603评论 0 51