Android内存管理机制
Android内存管理主要有:LowMemory Killer机制,Ashmem,PMEM/ION及Native内存和Dalvik内存管理管理和JVM垃圾回收机制。
LowMemory Killer机制
源码位置drivers/staging/Android/lowmemorykiller.c
Android是一个多任务系统,也就是说可以同时运行多个程序,这个大家应该很熟悉。一般来说,启动运行一个程序是有一定的时间开销的,因此为了 加快运行速度,当你退出一个程序时,Android并不会立即杀掉它,这样下次再运行该程序时,可以很快的启动。随着系统中保留的程序越来越多,内存肯定 会出现不足,low memory killer就是在系统内存低于某值时,清除相关的程序,保障系统保持拥有一定数量的空闲内存。
Low memorykiller根据两个原则,进程的重要性和释放这个进程可获取的空闲内存数量,来决定释放的进程。
进程的重要性,由task_struct->signal_struct->oom_adj决定,
Android将程序的重要性分成以下几类,按照重要性依次降低的顺序,每个程序都会有一个oom_adj值,这个值越小,程序越重要,被杀的可能性越低:
除了上述程序重要性分类之外,Android系统还维护着另外一张表minfree用于维护内存警戒值,这两张表构成一个对应关系,两张表在” /sys/module/lowmemorykiller/parameters/”下保存。
adj文件内容如下形如”0,1,2,4,9,15”,minfree文件内容形如”8192,10240,12288,14336,1638,20480”,这样形成的表结构如下:
例如,当系统可用内存小于12384*4K大小时,会开始杀掉oom_adj>=9级别的进程。
进程的内存,通过get_mm_rss获取,在相同的oom_adj下,内存大的,优先被杀。
Ashmem(匿名内存共享)
源码位置:kernel/mm/ashmem.c
为进程间提供提供大块共享内存,同时为内核提供回收和管理这个内存的机制。
相比于malloc和anonymous/namedmmap等传统的内存分配机制,其优势是通过内核驱动提供了辅助内核的内存回收算法机制 (pin/unpin)。什么是pin和unpin呢?具体来讲,就是当你使用Ashmem分配了一块内存,但是其中某些部分却不会被使用时,那么就可以 将这块内存unpin掉。
unpin后,内核可以将它对应的物理页面回收,以作他用。你也不用担心进程无法对unpin掉的内存进行再次访问,因为回收后的内存还可以再次被获得(通过缺页handler),因为unpin操作并不会改变已经 mmap的地址空间。
AndroidPMEM/ION内存管理器
源码位置:drivers/misc/pmem.c
AndroidPmem是为了实现共享大尺寸连续物理内存而开发的一种机制,该机制对dsp,gpu等部件非常有用。Pmem相当于把系统内存划分出一部分单独管理,即不被linux mm管理,实际上linux mm根本看不到这段内存。
Pmem和Ashmem都通过mmap来实现共享内存,其区别在于Pmem的共享区域是一段连续的物理内存,而Ashmem的共享区域在虚拟空间是 连续的,物理内存却不一定连续。dsp和某些设备只能工作在连续的物理内存上,这样cpu与dsp之间的通信就需要通过Pmem来实现。
ION是google在Android4.0ICS为了解决内存碎片管理而引入的通用内存管理器,它会更加融合kernel。目前QCOM MSM, NVDIA Tegra, TI OMAP, MRVL PXA都用ION替换PMEM。
ION 定义了四种不同的heap,实现不同的内存分配策略。
AndroidNative内存和Dalvik内存管理
Native进程是采用C/C++实现,不包含dalvik实例的进程,/system/bin/目录下面的程序文件运行后都是以native进程形式存在的,如/system/bin/surfaceflinger、/system/bin/rild、procrank等就是native进程,地址空间结构如下:
java进程是Android中运行于dalvik虚拟机之上的进程。dalvik虚拟机的宿主进程由fork()系统调用创建,所以每一个java进程 都是存在于一个native进程中,因此,java进程的内存分配比native进程复杂,因为进程中存在一个虚拟机实例。Android系统中的应用程 序基本都是java进程,如桌面、电话、联系人、状态栏等等,进程结构如下:
Stack空间(进栈和出栈)由操作系统控制,其中主要存储函数地址、函数参数、局部变量等等,所以Stack空间不需要很大,一般为几MB大小。
Heap空间的使用由程序员控制,程序员可以使用malloc、new、free、delete等函数调用来操作这片地址空间。Heap为程序完成 各种复杂任务提供内存空间,所以空间比较大,一般为几百MB到几GB。正是因为Heap空间由程序员管理,所以容易出现使用不当导致严重问题。
进程空间中的heap空间是我们需要重点关注的。heap空间完全由程序员控制,我们使用的malloc、C++ new和java new所申请的空间都是heap空间, C/C++申请的内存空间在native heap中,而java申请的内存空间则在dalvik heap中。
进程的内存空间只是虚拟内存(或者叫作逻辑内存),而程序的运行需要的是实实在在的内存,即物理内存(RAM)。在必要时,操作系统会将程序运行中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。
RAM作为进程运行不可或缺的资源,对系统性能和稳定性有着决定性影响。另外,RAM的一部分被操作系统留作他用,比如显存等等,内存映射和显存等都是由操作系统控制,我们也不必过多地关注它,进程所操作的空间都是虚拟地址空间,无法直接操作RAM。示意图如下:
Android系统对dalvik的vm heapsize作了硬性限制,当java进程申请的java空间超过阈值时,就会抛出OOM异常,在/system/build.prop中有三项可以配置相关信息。
dalvik.vm.heapstartsize:堆分配的初始大小,调整这个值会影响到应用的流畅性和整体ram消耗。这个值越小,系统ram消 耗越慢,但是由于初始值较小,一些较大的应用需要扩张这个堆,从而引发gc和堆调整的策略,会应用反应更慢。相反,这个值越大系统ram消耗越快,但是程序更流畅。
dalvik.vm.heapgrowthlimit:受控情况下的极限堆(仅仅针对dalvik堆,不包括native堆)大小,dvm heap是可增长的,但是正常情况下dvm heap的大小是不会超过dalvik.vm.heapgrowthlimit的值(非正常情况下面会详细说明)。这个值控制那些受控应用的极限堆大小, 如果受控的应用dvm heap size超过该值,则将引发oom(out of memory)。
dalvik.vm.heapsize:不受控情况下的极限堆大小,这个就是堆的最大值。不管它是不是受控的。这个值会影响非受控应用的dalvikheap size。一旦dalvik heap size超过这个值,直接引发oom。
JVM垃圾回收机制
JVM的垃圾原理是这样的,它把对象分为年轻代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的垃圾回收算法。
年轻代(Young):年轻代分为三个区,一个eden区,两个Survivor区。程序中生成的大部分新的对象都在Eden区中,当Eden区满 时,还存活的对象将被复制到其中一个Survivor区,当此Survivor区的对象占用空间满了时,此区存活的对象又被复制到另外一个 Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到年老代。
年老代(Tenured):年老代存放的是上面年轻代复制过来的对象,也就是在年轻代中还存活的对象,并且区满了复制过来的。一般来说,年老代中的对象生命周期都比较长。
持久代(Perm):用于存放静态的类和方法,持久代对垃圾回收没有显著的影响。
Android内存分析工具
内存监控工具DDMS
Ddms可以在eclipse中安装对应的插件,同时在androidsdk的tools文件夹也有同样的工具,并且功能比eclipse插件更完备。
切换到eclipse的ddms视图后,从设备中选择要监控的进程(手机需要开启usb调试,被监控应用的manifest中android:debuggable应该为true)
如下图操作,我们主要关注dataobject类型的total size数据在使用过程中是否出现异常的数据波动。
Eclipse MAT插件
Mat是eclipse的一个插件,安装完成之后,在ddms试图下,同时在左侧设备试图上方选择“Update Heap”和“Dump HPROF file”按钮,在弹出的窗口中选择“Leak Suspects Report”并finish,即可出现下面的结果试图:
进入leaksuspects之后,就能列出所有可能有泄漏的地方以及代码片段
Adb shell的dumpsys meminfo命令
Adbshell的cat命令
应用内存分配跟踪工具Allocation tracker
同样该功能用到ddms ,只是里面提供的一个子功能而已,能够知道所有对象的分配是在代码的哪个类,哪个文件的哪一行。
AndroidOOM的常见原因
非静态内部类的静态实例容易造成内存泄漏
activity使用静态成员
使用handler时的内存问题
注册某个对象后未反注册
集合中对象没清理造成的内存泄露
资源对象没关闭造成的内存泄露
一些不良代码成内存压力:Bitmap使用不当;构造Adapter时,没有使用缓存的 convertView;在经常调用的方法中创建对象(例如循环)
参考资料:
Android系统匿名共享内存Ashmem(AnonymousShared Memory)简要介绍和学习计划 - 老罗的Android之旅 - 博客频道 - CSDN.NET
Android系统匿名共享内存Ashmem(AnonymousShared Memory)在进程间共享的原理分析 - 老罗的Android之旅 - 博客频道 - CSDN.NET
来源:oschina
链接:https://my.oschina.net/u/172402/blog/367621