Android 内存管理,主要包括两个方面,一是系统级内存管理,二是应用级内存管理,下面将分别介绍这两方面内容。
一、Android系统级内存管理
系统级别内存管理是以进程为粒度进行管理,下面以进程为粒度,介绍进程的优先级和系统在内存较低时如何回收这些进程。
1 Android进程特点
1.1 每一个Android应用程序进程都是由Zygote的进程fork出来的,更详细的技术原理可以参考: http://blog.csdn.net/luoshengyang/article/details/8923484
1.2 每一个Android应用程序进程都有一个Dalvik虚拟机实例。这样做的好处是Android应用程序进程之间不会相互影响,一个Android应用程序进程的意外终止,不会影响到其他的Android应用程序进程的正常运行。
2 Android进程的优先级
Android系统会根据进程中的组件和这些组件的状态来判断这些进程的优先级,在系统内存达到规定的不同的level阈值时触发清空不同level的进程类型。
进程优先级分类:
-
Foreground process
用户当前操作所必须的进程。通常在任意给定的时间,前台进程数量很少。只有在内存不足以支持他们同时继续运行的万不得已的情况下,系统才会终止前台进程。
以下几种类型都是前台进程:
a: 拥有用户正在交互的Activity(已调用onResume()但没收到onPause()调用的Activity)
b: 拥有某个Service,后者绑定到用户正在交互的Activity
c: 拥有正在前台运行的service(服务已调用startForeground())
d: 拥有正在执行一个生命周期回调的Service(onCreate()、onStart()或者onDestroy())
e: 拥有正在执行onReceive()方法的BroadcastReceiver
-
Visible process
没有任何前台组件,但仍会影响用户在屏幕上所见内容的进程。可见进程被视为及其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程,以下类型是可见进程。
a: 拥有不在前台,但仍对用户可见的Activity(在onPause()状态)b: 拥有绑定到可见(或前台)Activity的Service
-
Service process
正在运行startService()方法启动的服务,且不属于上述两个更高类别进程的进程
服务进程与用户所见内容没有直接关联,但是通常在执行一些用户关系的操作(比如,在后台播放音乐或者从网上下载数据)因此,出发内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。 -
Background process
后台进程对用户体验没有直接影响,系统可随时终止它们,以回收内存供前台进程,可见进程或者服务进程使用。通常会有很多后台进程在运行,因此它们会保存在LRU列表中,以确保包含用户最近查看的Activity的进程最后一个被终止。如果某个Activity正确的实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因此当用户导航回到该Activity时,Activity会回复其所有可见状态。对用户不可见的Activity进程(已调用Activity的onStop()方法)。 -
Empty process
这种类型的进程存在的唯一目的是用作缓存,以缩短下次在其运行组件所需要的启动时间。为使总体系统资源在进程缓存和底层缓存之间保持平衡,系统往往会终止这些进程,这些进程不包含任何活动应用组件
3. Android进程回收机制
当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”。从而使得用户调用程序时能够在第一时间得到响应。当系统内 存不足时,系统将激活内存回收过程,就会清除掉一个进程来提供内存,被杀掉的进程内的所有组件都会立刻被销毁。
为了不因内存回收影响用户体验(如杀死当前的活动进程),Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级。并且Android将所有进程放进LUR cache 中,并根据进程的优先级、和所占用内存大小来杀死进程。
4. 进程回收Lowmemorykiller
Android中对于内存的回收,主要是依靠Lowmemorykiller来完成,这是一种根据OOM_ADJ阈值级别触发相应力度的内存回收机制。
定义在ProcessList.java文件,OOM_ADJ划分为16级,从-17到16,具体取值如下表:
ADJ级别
|
取值
|
解释
|
---|---|---|
UNKNOWN_ADJ | 16 | 一般指将要会缓存进程,无法获取确定值 |
CACHED_APP_MAX_ADJ | 15 | 不可见进程的adj最大值 1 |
CACHED_APP_MIN_ADJ | 9 | 不可见进程的adj最小值 2 |
SERVICE_B_AD | 8 | B List中的Service(较老的、使用可能性更小) |
PREVIOUS_APP_ADJ | 7 | 上一个App的进程(往往通过按返回键) |
HOME_APP_ADJ | 6 | Home进程 |
SERVICE_ADJ | 5 | 服务进程(Service process) |
HEAVY_WEIGHT_APP_ADJ | 4 | 后台的重量级进程,system/rootdir/init.rc文件中设置 |
BACKUP_APP_ADJ | 3 | 备份进程 3 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知进程,比如后台音乐播放 4 |
VISIBLE_APP_ADJ | 1 | 可见进程(Visible process) 5 |
FOREGROUND_APP_ADJ | 0 | 前台进程(Foreground process) 6 |
PERSISTENT_SERVICE_ADJ | -11 | 关联着系统或persistent进程 |
PERSISTENT_PROC_ADJ | -12 | 系统persistent进程,比如telephony |
SYSTEM_ADJ | -16 | 系统进程 |
NATIVE_ADJ | -17 | native进程(不被系统管理) |
Lowmemorykiller回收内存时,会根据进程的级别优先杀死OOM_ADJ比较大(OOM_ADJ>=4)的进程,对于优先级相同的进程则进一步收到进程所占用内存和进程存活时间的影响。
lmk策略
Lowmemorykiller根据当前可用内存情况来进行进程释放,总设计了6各级别,Lowmemorykiller杀进程的6档,如下表所示:
-
- CACHED_APP_MAX_ADJ 不可见进程adj最大值
- CACHED_APP_MIN_ADJ 不可见进程adj最小值
- BACKUP_APP_ADJ 备份进程
- PERCEPTIBLE_APP_ADJ 可感知进程,比如后台引用播放
- VISIBLE_APP_ADJ 可见进程
- FOREGROUND_APP_ADJ 前台进程
系统内存从很宽裕到不足,Lowmemorykiller也会相应的从cached_app_max_adj(第一档)开始杀进程,如果内存还不足,继续杀死第二档的,不断深入,直到满足内存阈值条件。
二、Android应用级内存管理
应用级内存管理即应用内的内存管理,由于Android使用java语言,因此应用内的内存管理就是指虚拟机对内存的分配和管理,具体可以参考Java内存分配与回收原理
2.1 Android内存分配和回收
Android给每个应用分配独立的虚拟机,并且规定了虚拟机的对内存阈值,达到这个内存要求再要分配更多时就会抛出OOM,JVM宁愿抛出异常也不愿杀死可能有用的对象, 可以通过adb shell getprop | grep heap查看可分配的最大内存。
adb shell getprop | grep heap
[dalvik.vm.heapgrowthlimit]: [128m]
[dalvik.vm.heapmaxfree]: [32m]
[dalvik.vm.heapminfree]: [8m]
[dalvik.vm.heapsize]: [512m]
[dalvik.vm.heapstartsize]: [8m]
[dalvik.vm.heaptargetutilization]: [0.75]
Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发gc操作,从而腾出更多空闲 的内存空间。
GC之后, VM会遍历Heap找到不被使用的pages, 通过madvise函数将其返回给内核, 从而释放这块被逻辑Heap使用的物理内存.
2.2 Android虚拟机的GC
Android中的Dalvik和ART采用的是不同的GC,此处介绍ART的GC。
Android中堆内存氛围新生代,老生代,永久代,系统在某一代的内存占满时会触发GC,触发暂停的时间依赖于在某代和有多少可回收的对象。
ART在堆内存里,采用的是分代收集算法。所有Java堆可以细分为:新生代和老年代。在细致分就是把新生代分为:Eden空间、From Survivor空间、To Survivor空间。当堆无法再扩展时,会抛出OutOfMemoryError异常。
分代收集算法中,新生代的特点是对象会很快回收,因此,新生代使用的是复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间,老年代则采取标记-压缩算法。
执行GC的时候,所有的线程的任何操作都会需要暂停,等待GC操作完成之后,其他操作才能够继续运行,如下图所示:
因此,通常来说,单个的GC并不会占用太多的时间,但是大量的不同的GC操作会显著占用帧间隔时间,如果在帧间隔时间里面做了过多的GC,渲染等操作的可用时间就减少了。
三、总结
本文主要介绍了Android如何管理内存,在系统级别以进程为回收单位,在应用内有最大堆内存的现在,因此,做优化的需要可以从以下两个方面来衡量:
1 保证进程所占用的内存越少,存活的概率越大
2 保证JVM堆上占用的内存越少,就越不容易发生OOM。