Android 性能优化 内存优化 How Android Managers Memory

前言

在上篇 Android 性能优化 内存优化 基本概念对Android整个系统有了初步认识,即Android在系统上做了哪些操作来节约内存,这篇文章就主要介绍Android是如何进行内存管理的。

在 Android应用开发性能优化完全分析这篇文章中对Android内存性能优化分析中,主要从两个方面进行分析,一是系统级内存管理,二是应用级内存管理。而这种分法是最清晰易懂的,所以本篇文章也从这两个方面入手。

1. Android系统级内存管理

系统级内存管理即以进程为粒度进行管理。接下来以进程为粒度,介绍进程的优先级和系统在内存较低时如何回收这些进程。

1.1 Android进程两大特点:

  1. 第一个特点是每一个Android应用程序进程都由Zygote的进程fork出来的。
  2. 第二个特点是每一个Android应用程序进程都有一个Dalvik虚拟机实例。这样做的好处是Android应用程序进程之间不会相互影响,也就是说,一个Android应用程序进程的意外中止,不会影响到其它的Android应用程序进程的正常运行。

1.2 Android进程的优先级

the system places each process into an “importance hierarchy” based on the components running in the process and the state of those components.
Android系统会根据进程中的组件及这些组件的状态来判断这些进程的优先级,在系统内存达到规定的不同level阈值时触发清空不同level的进程类型。

进程的优先级如下:

  • Foreground process
    用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。

    • 拥有用户正在交互的 Activity(已调用onResume())
    • 拥有某个 Service,后者绑定到用户正在交互的 Activity
    • 拥有正在“前台”运行的 Service(服务已调用 startForeground())
    • 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
    • 拥有正执行其 onReceive() 方法的 BroadcastReceiver
  • Visible process
    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

    • 拥有不在前台、但仍对用户可见的 Activity(已调用onPause())。
    • 拥有绑定到可见(或前台)Activity 的 Service
  • Service process
    尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

    • 正在运行startService()方法启动的服务,且不属于上述两个更高类别进程的进程。
  • Background process
    后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在LRU列表中,以确保包含用户最近查看的Activity的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

    • 对用户不可见的Activity的进程(已调用Activity的onStop()方法)
  • Empty process
    保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

    • 不含任何活动应用组件的进程

官方文档;Processes and Threads
Android 进程优先级详解
Android平台App进程优先级

1.3 Android进程回收机制

  • Android might decide to shut down a process at some point, when memory is low and required by other processes that are more immediately serving the user. Application components running in the process that’s killed are consequently destroyed. A process is started again for those components when there’s again work for them to do.
    当 Android 应用程序退出时,并不清理其所占用的内存,Linux 内核进程也相应的继续存在,所谓“退出但不关闭”。从而使得用户调用程序时能够在第一时间得到响应。当系统内存不足时,系统将激活内存回收过程,就会清除掉一个进程来提供内存,被杀掉的进程内的所有组件都会立刻被销毁。

  • When users switch between apps, Android keeps apps that are not foreground in a least-recently used (LRU) cache.
    As the system runs low on memory, it kills processes in the LRU cache beginning with the process least recently used. The system also accounts for processes that hold onto the most memory and can terminate them to free up RAM.
    为了不因内存回收影响用户体验(如杀死当前的活动进程),Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级。并且Android将所有进程放进LUR cache中,并根据进程的优先级、和所占用内存大小来杀死进程。

  • When the system begins killing processes in the LRU cache, it primarily works bottom-up. The system also considers which processes consume more memory and thus provide the system more memory gain if killed. The less memory you consume while in the LRU list overall, the better your chances are to remain in the list and be able to quickly resume.
    当系统开始回收进程时,你的进程所占用的内存越少,存活的概率就越大。

官方文档:Switching apps

其中ActivityManagerService 集中管理所有进程的内存资源分配。所有进程需要申请或释放内存之前必须调用 ActivityManagerService 对象,获得其“许可”之后才能进行下一步操作,或者 ActivityManagerService 将直接“代劳”,通过trimApplications()方法进行内存回收。

Android中对于内存的回收,主要依靠Lowmemorykiller来完成,是一种根据阈值级别触发相应力度的内存回收的机制。
Android 性能优化 内存优化 How Android Managers Memory_第1张图片

Lowmemorykiller根据当前可用内存情况来进行进程释放,总设计了6个级别,即Lowmemorykiller的杀进程的6档,如下:
1. CACHED_APP_MAX_ADJ:不可见进程的adj最大值
2. CACHED_APP_MIN_ADJ:不可见进程的adj最小值
3. BACKUP_APP_ADJ:备份进程
4. PERCEPTIBLE_APP_ADJ:可感知进程,比如后台音乐播放
5. VISIBLE_APP_ADJ:可见进程(Visible process)
6. FOREGROUND_APP_ADJ:前台进程(Foreground process)

系统内存从很宽裕到不足,Lowmemorykiller也会相应地从CACHED_APP_MAX_ADJ(第1档)开始杀进程,如果内存还不足,那么会杀CACHED_APP_MIN_ADJ(第2档),不断深入,直到满足内存阈值条件。

Android进程生命周期与ADJ
IBM: Android 操作系统的内存回收机制
Android进程的内存管理分析

Android进程回收机制就介绍到这,IBM上的这篇文章将系统级内存回收分为了默认内存回收机制内核级内存回收。详细可查看这篇文章。因为并没有了解源码,所以就不误人子弟了。但也从此看出,了解源码是很重要的。

接下来就介绍Android应用级内存管理。

2. Android应用级内存管理

应用级内存管理即应用内,即虚拟机对内存的分配和管理。这块可以看周志明的《深入理解Java虚拟机》了解更多。

2.1 Android内存分配和回收

Android给每个应用分配独立的虚拟机,并且规定了虚拟机的堆内存阈值,达到这个内存再要分配更多时就会抛出OOM,JVM宁愿抛出异常也不愿杀死可能有用的对象。可通过adb shell getprop | grep heap可查看所分配的最大内存。

  • The logical size of the heap is not the same as the amount of physical memory used by the heap. When inspecting your app’s heap, Android computes a value called the Proportional Set Size (PSS), which accounts for both dirty and clean pages that are shared with other processes—but only in an amount that’s proportional to how many apps share that RAM. This (PSS) total is what the system considers to be your physical memory footprint. For more information about PSS, see the Investigating Your RAM Usage guide.
    Android所分配的堆内存的逻辑大小并不等于实际大小。Android使用PSS(Proportional Set Size)来表示该进程所占用的物理内存。其中Proportional Set Size(PSS)记录了应用程序自身占用以及和其他进程进行共享的内存。

  • The Dalvik heap does not compact the logical size of the heap, meaning that Android does not defragment the heap to close up space. Android can only shrink the logical heap size when there is unused space at the end of the heap.
    However, the system can still reduce physical memory used by the heap. After garbage collection, Dalvik walks the heap and finds unused pages, then returns those pages to the kernel using madvise.
    Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发gc操作,从而腾出更多空闲的内存空间。
    GC之后, VM会遍历Heap找到不被使用的pages, 通过madvise函数将其返回给内核, 从而释放这块被逻辑Heap使用的物理内存.

Allocating and Reclaiming App Memory
View overall memory allocations

2.2 Android虚拟机的GC

Android中Dalvik和ART采用的是不同的GC,在 官方文档:GC 和 ART and Dalvik中也有介绍(这里主要是ART的):

  • Android’s memory heap is a generational one, meaning that there are different buckets of allocations that it tracks, based on the expected life and size of an object being allocated. For example, recently allocated objects belong in the *Young generation. When an object stays active long enough, it can be promoted to an older generation, followed by a permanent generation.*
    Android中堆内存分为新生代,老生代,永久代。

  • Each heap generation has its own dedicated upper limit on the amount of memory that objects there can occupy. Any time a generation starts to fill up, the system executes a garbage collection event in an attempt to free up memory. The duration of the garbage collection depends on which generation of objects it’s collecting and how many active objects are in each generation.
    系统在某一代的内存占满时会触发GC,触发暂停的时间依赖于在某代和有多少可回收的对象。

ART在堆内存里,采用的是分代收集算法。所有Java堆可以细分为:新生代和老年代。在细致分就是把新生代分为:Eden空间、From Survivor空间、To Survivor空间。当堆无法再扩展时,会抛出OutOfMemoryError异常。
分代收集算法中,新生代的特点是对象会很快回收,因此,新生代使用的是复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老生代的内存空间,老年代则采取标记-压缩算法。

2.3 垃圾收集算法介绍

  • 任何一种垃圾回收算法一般要做2件基本的事情:

    1. 发现无用信息对象;
    2. 回收被无用对象占用的内存空间,使该空间可被程序再次使用。
  • 判断对象是否无用的两种算法:

    1. 引用计数算法(Reference Counting Collector)
    2. 根搜索算法(JVM采用)
      在Java中可作为根的有:
      • 虚拟机栈中引用的对象(本地变量表)
      • 方法区中静态属性引用的对象
      • 方法区中常量引用的对象
      • 本地方法栈中JNI引用的对象(Native对象)
  • JVM采用的垃圾回收算法有:

    1. 标记-清除算法(mark and sweep)
    2. 标记-整理算法(mark and compact)
    3. 复制算法(copying)
    4. 分代算法(generation算法)
  • JVM的内存管理更多请参考:
    周志明的《深入理解Java虚拟机》
    Java虚拟机详解—-JVM常见问题总结
    JVM理解其实并不难!
    GC那些事儿–Android内存优化第一弹
    深入理解 Java 垃圾回收机制
    Android GC 那点事

结语:

本篇文章梳理了Android是如何管理内存的,在系统级别以进程为回收单位,在应用内有最大堆内存限制,对这些有了个概念后,当我们写代码时、进行内存优化时心里都会有个谱。
保证进程所占用的内存越少,存活的概率就越大。
保证JVM堆上占用的内存越少,就越不容易OOM。

所在在了解到这块后,下一篇就主要学习怎样进行内存优化了。

其中在学习这块知识时,大部分都涉及到了framework等,如果能很好的理解源码,基本功扎实的话,对这块大有裨益,也该要往深里学了。同时也是因为个人水平原因,文章若有错误还请指出。

你可能感兴趣的:(性能优化,项目架构)