内存管理机制

从操作系统的角度来说,内存就是一块数据存储区域,属于可被操作系统调度的资源。现代多任务(进程)的操作系统中,内存管理尤为重要,操作系统需要为每一个进程合理的分配内存资源,所以可以从两方面来理解操作系统的内存管理机制。
第一:分配机制。为每一个进程分配一个合理的内存大小,保证每一个进程能够正常的运行,不至于内存不够使用或者每个进程占用太多的内存。
第二:回收机制。在系统内存不足打的时候,需要有一个合理的回收再分配的机制,以保证新的进程可以正常运行。回收的时候就要杀死那些正在占有内存的进程,操作系统需要提供一个合理的杀死这些进程的机制,以保证更少的副作用。

Android系统也遵从上述的机制,官网对Android内存管理的描述如下:
Android并没有为内存提供交换区(Swap space),但是它有使用paging与memory-mapping(mmapping)的机制来管理内存。这意味着任何你修改的内存(无论是通过分配新的对象还是去访问mmaped pages中的内容)都会贮存在RAM中,而且不能被paged out。因此唯一完整释放内存的方法是释放那些你可能hold住的对象的引用,当这个对象没有被任何其他对象所引用的时候,它就能够被GC回收了。只有一种例外是:如果系统想要在其他地方重用这个对象。(整体也分成两部分:分配和回收)

Android官网对Android内存管理的概述

分页(Paging) 是一种操作系统里内存管理的一种技术,可以使电脑的主存可以使用存储在辅助内存中的数据。操作系统会将辅助内存(通常是磁盘)中的数据分区成固定大小的区块,称为“页”(pages)。当不需要时,将分页由主存(通常是内存)移到辅助内存;当需要时,再将数据取回,加载主存中。分页/虚拟内存能有助“大大地”降低整体及额外非必要的 I/O 次数,提高系统整体运作性能。分页是虚拟内存技术中的重要部分。

内存映射(Memory-mapped file) 是一段虚内存逐字节对应于一个文件或类文件的资源,使得应用程序处理映射部分如同访问主内存。

内存分配机制

大家先想一个问题,假设有一个内存为1G的Android设备,上面运行了一个非常非常吃内存的应用,如果没有任何机制的情况下是不是用着用着整个设备会因为我们这个应用把1G内存吃光然后整个系统运行瘫痪呢?

当然,Google的工程师才不会这么傻的把系统设计这么差劲。为了使系统不存在我们上面假想情况且能安全快速的运行,Android的框架使得每个应用程序都运行在单独的进程中。

Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动,并载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程,系统会fork Zygote进程生成一个新的进程,然后在新的进程中加载并运行应用程序的代码。framework的代码和资源在应用的所有进程之间进行共享。

如果应用在运行时再存在上面假想的情况,那么瘫痪的只会是自己的进程,不会直接影响系统运行及其他进程运行。

每个应用进程都对应自己唯一的虚拟机实例。

既然每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。

如果你的应用占用内存空间已经接近这个阈值,此时再尝试分配内存的话,很容易引发OutOfMemoryError错误。

Android对内存的使用方式是“尽最大限度的使用”,这一点继承了Linux的优点。但也有些不同,Android会在内存中保存尽可能多的数据,即使有些进程不再使用了,但是它的数据还被存储在内存中,所以Android现在不推荐显式的“退出”应用。因为这样,当用户下次再启动应用的时候,只需要恢复当前进程就可以了,不需要重新创建进程,这样就可以减少应用的启动时间。

Android系统并不会对Heap中空闲内存区域做碎片整理。系统仅仅会在新的内存分配之前判断Heap的尾端剩余空间是否足够,如果空间不够会触发GC操作,从而腾出更多空闲的内存空间。在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型,最近分配的对象会存放在Young Generation区域。当这个对象在该区域停留的时间达到一定程度,它会被移动到Old Generation,最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的GC操作。例如,刚分配到Young Generation区域的对象通常更容易被销毁回收,同时在Young Generation区域的GC操作速度会比Old Generation区域的GC操作速度更快,如下图:

综上所述,Android内存的分配机制要点如下:
1、每个应用程序都运行在单独的进程中
2、应用程序的进程从Zygote进程fork出来
3、每个应用进程都对应自己唯一的虚拟机实例
4、每个虚拟机都有堆内存阈值限制
5、即使进程退出了,数据仍然在内存中
6、对象根据创建的时间放置在young、old、permanent三个区

内存回收机制

在Android系统中,每一个Generation的内存区域都有固定的大小。随着新的对象陆续被分配到此区域,当对象总的大小临近这一级别内存区域的阀值时,会触发GC操作,以便腾出空间来存放其他新的对象

通常情况下,GC发生的时候,所有的线程都是会被暂停的。执行GC所占用的时间和它发生在哪一个Generation也有关系,Young Generation中的每次GC操作时间是最短的,Old Generation其次,Permanent Generation最长。执行时间的长短也和当前Generation中的对象数量有关,遍历树结构查找20000个对象比起遍历50个对象自然是要慢很多的。

GC操作由Dalvik虚拟机执行。

Android系统内存回收示意图如下:

上面说的是一个进程内的内存回收,如果整个系统的内存快满了该如何回收呢?

我们知道,Android对内存的使用方式是“尽最大限度的使用”,这一点继承了Linux的优点。Android会在内存中保存尽可能多的数据,即使有些进程不再使用了,但是它的数据还被存储在内存中,所以Android现在不推荐显式的“退出”应用。因为这样,当用户下次再启动应用的时候,只需要恢复当前进程就可以了,不需要重新创建进程,这样就可以减少应用的启动时间。只有当Android系统发现内存不够使用,需要回收内存的时候,Android系统就会需要杀死其他进程,来回收足够的内存。但是Android也不是随便杀死一个进程,比如说一个正在与用户交互的进程,这种后果是可怕的。所以Android会有限清理那些已经不再使用的进程,以保证最小的副作用。
Android杀死进程有两个参考条件:进程优先级和回收收益

进程优先级

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要移除旧进程来回收内存。 为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。

重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程):

前台进程是用户当前操作所必需的进程,如果一个进程满足以下任一条件,即视为前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,后者绑定到用户正在交互的 Activity
托管正在“前台”运行的 Service(服务已调用 startForeground())
托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver
通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

可见进程指没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
托管绑定到可见(或前台)Activity 的 Service。
可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

服务进程指正在运行,已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

后台进程指包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 有关保存和恢复状态的信息,请参阅 Activity文档。

空进程不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

根据进程中当前活动组件的重要程度,Android 会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。

回收收益

当Android系统开始杀死LRU缓存中的进程时,系统会判断每个进程杀死后带来的回收收益。因为Android总是倾向于杀死一个能回收更多内存的进程,从而可以杀死更少的进程,来获取更多的内存。杀死的进程越少,对用户体验的影响就越小。

Android内存优化—Android的内存管理方式相关推荐

  1. 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 内存复用 | 弱引用 | 引用队列 | 针对不同 Android 版本开发不同的 Bitmap 复用策略 | 工具类代码 )

    文章目录 一.Bitmap 复用池 二.弱引用 Bitmap 内存释放 三.从 Bitmap 复用池中获取对应可以被复用的 Bitmap 对象 1.Android 2.3.3(API 级别 10)及以 ...

  2. 【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )

    文章目录 一. Java 虚拟机内存模型 二. 程序计数器 ( 线程私有区 ) 三. 虚拟机栈 ( 线程私有区 ) 四. 本地方法栈 ( 线程私有区 ) 五. 方法区 ( 共享数据区 ) 1. 方法区 ...

  3. Android性能优化系列之内存优化

    在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能,上篇博客,我介 ...

  4. 【Android 内存优化】Bitmap 内存缓存 ( Bitmap 缓存策略 | LruCache 内存缓存 | LruCache 常用操作 | 工具类代码 )

    文章目录 一.Bitmap 内存缓存策略 二.LruCache 内存缓存 三.LruCache 常用操作 四.LruCache 工具类 五.源码及资源下载 官方参考 : Google 官方提供的 内存 ...

  5. android加载大量图片内存优化,Android图片加载内存优化

    利用BitmapFactory.Options实现图片内存优化 通过设置options.inPreferredConfig控制内存占用 首先准备了一张1280x800的blue_bg.png图片,我们 ...

  6. Android 性能优化 - 彻底解决内存泄漏

    起源 有趣的灵魂千奇百怪,内存泄漏的也是各式各样 我在15年写过一遍 文章 < android中常见的内存泄漏和解决办法>http://blog.csdn.net/wanghao20090 ...

  7. 【Android 内存优化】Bitmap 内存占用计算 ( Bitmap 图片内存占用分析 | Bitmap 内存占用计算 | Bitmap 不同像素密度间的转换 )

    文章目录 一.Bitmap 内存占用 二.Bitmap 内存占用计算示例 三.Bitmap 内存占用与像素密度 四.Bitmap 内存占用与像素密度示例 一.Bitmap 内存占用 在 Android ...

  8. 11 操作系统第三章 内存管理 内存的基本知识 内存管理 内存空间扩充 连续分配管理方式

    文章目录 1 内存概念 1.1 内存作用 1.2 逻辑地址VS物理地址 1.3 装入的三种方式 1.3.1 绝对装入 1.3.2 可重定位装入 1.3.3 动态重定位装入 1.4 链接的三种方式 1. ...

  9. Android兼容性优化-Android 8.0设置Activity透明主题崩溃

    原文连接:https://mp.weixin.qq.com/s/g6RzzJIOpyBLiCq-YHBBMg 崩溃日志: 1 java.lang.RuntimeException:Unable to ...

最新文章

  1. HDU 1495 非常可乐
  2. html 显示状态条,怎么控制html5 video 控制条显示和隐藏时间
  3. Python: 反方向迭代一个序列
  4. 第四章:Django模型——添加 Event发布会的表 报错
  5. 问题-[Delphi]用LoadLibrary加载DLL时返回0的错误
  6. Struts结合梅花雪实现动态生成树
  7. 用glew,glfw实现opengl-学习笔记3着色器
  8. RHEL6.3基本网络配置(4) 其它常用网络配置文件
  9. jmeter测试mysql数据库_【JMeter】JMeter完成一个MySql压力测试
  10. uniapp 压缩图片(微信小程序)
  11. linux内核tc,Linux 内核流量控制 TC 详解
  12. (收藏自己看)程序员的工作不能用“生产效率”这个词来衡量
  13. PIC16F887 单片机 接线 实物器件说明 原理
  14. pythoneducoder苹果梨子煮水的功效_荸荠和梨子一起煮的好处
  15. JAVA设计模式第三讲:结构型设计模式
  16. 为什么软件开发周期总是预估的2~3倍?
  17. 前端动态生成随机图形验证码
  18. cisco在服务器编辑首页信息,cisco设置
  19. 【Python数据分析与可视化】期末复习笔记整理(不挂科)
  20. 安装VisualSVN server

热门文章

  1. 网络设备维保状态查询
  2. 小case:轻松解决企业建站的五大基础问题
  3. js之onreadystatechange事件
  4. Py-Tetrazine-Py-Amide-Butyric acid相关介绍,1233234-76-8
  5. 分析Padavan的代码三
  6. An operation is not implemented: not implemented被坑之路[Kotlin]
  7. C# internal解析
  8. 盛大网盘出现故障,无法正常访问
  9. Keymob成为国际最大的移动广告平台
  10. 计算机名师工作室活动个人总结,名师工作室个人年度工作计划范文(通用3篇)...