Android开发Android开发Android进阶之路

Android开发内存管理

2023-02-11  本文已影响0人  谁动了我的代码

Android内存优化是性能优化中很重要的一部分,而避免内存溢出(OOM)又是内存优化中比较核心的一点。本篇主要介绍内存占用与OOM相关的知识点。

Android内存管理机制

Google在Android官网上初步介绍了Android系统是如何管理进程间的内存分配管理应用内存。Android 运行时(ART)和Dalvik虚拟机使用paging和memory-mapping来管理内存。下面简要概述一些Android系统中重要的内存管理基础概念。

共享内存

Android系统通过下面几种方式来实现共享内存:

分配与回收应用内存

限制应用的内存

应用切换操作

内存监控

内存监控的主要指标为:内存占用、OOM。

内存占用情况

通过命令行查看内存占用情况:

adb shell dumpsys meminfo -a com.efs.demo

image

通过Android Studio的Profiler工具查看内存占用情况(可参考:分析内存使用情况)。

image

内存占用指标

主要指标如下:

字段 指标含义 获取方式
JavaHeap Java内存占用 Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()
JavaHeapUsedRate Java内存占用率 JavaHeap/Runtime.getRuntime().maxMemory()
Graphics 显存 Debug.MemoryInfo.getMemoryStat("summary.graphics")
VMSize 虚拟内存 /proc/进程pid/status
TotalPss 物理内存 Debug.MemoryInfo.getTotalPss()
DalvikPss Java物理内存 Debug.MemoryInfo.dalvikPss
NativePss Native物理内存 Debug.MemoryInfo.nativePss

OOM产生条件

待申请的内存大于系统分配给应用的剩余内存

OOM原因归类

对于Android平台,OOM主要有如下原因:

image

内存优化三方面

主要从以下方面进行优化:

优化大对象

减小新分配出来的对象占用内存的大小,使用轻量的对象。

数据结构可以考虑使用SparseArray而不是HashMap等。

合理复用对象

合理的缓存和复用对象。

Android系统本身内置了很多的资源,如字符串、颜色、动画、样式等,都可以在应用中直接引用。

在ImageView等显示大量图片的控件里,需要使用LRU Cache的机制来缓存处理Bitmap。

onDraw等频繁调用的方法,避免创建对象,因为他会迅速增加内存的使用,而且很容易引起频繁的gc,甚至是内存抖动。

避免对象泄露

内存泄漏,会导致一些不再使用的对象无法及时释放,很容易导致后续需要分配内存的时候,剩余空间不足而出现OOM。

临时创建的Bitmap对象,在经过变换得到新的Bitmap对象之后,应该回收原始的Bitmap。

Android应用中有许多需要register与unregister的监听器,需确保使用后在合适的时机调用unregister注销监听器。

Cursor对象使用后,及时的调用close()。

操作系统内存管理基础

不论什么操作系统,内存管理都是绝对的重点和难点。内存管理旨在为系统中所有 Task 提供稳定可靠的内存分配、释放和保护机制。你可能会疑问,学习 Android 系统有必要了解 Linux Kernel 的内存管理机制吗?

是的!不论是 Android 的音频系统、GUI 系统,还是 Binder 的实现机理等,都是和内存管理息息相关的。

虚拟内存

虚拟内存就是当内存资源不足时,借用硬盘中的一部分的空间,充当内存使用。系统会挑选优先级低的内存数据放入硬盘,后续若要用到硬盘中的数据,系统会产生一次缺页中断,然后把数据交换回内存中。

要理解虚拟内存机制,就要理解三种地址空间,分别是逻辑地址、线性地址和物理地址:

1.逻辑地址(Logical Address)

逻辑地址是程序编译后产生的地址,也称为相对地址,由两部分组成:

段选择子(Segment Selector):描述逻辑地址所处的段

Offset:描述所在段内的偏移值

2.线性地址(Linear Address)

线性地址是由逻辑地址经过分段机制转换后得到的。

大致转换过程为:通过段选择子确定段的基地址,然后结合 Offset 得到线性地址。

3.物理地址(Physical Address)

物理地址就是指机器真实的物理内存地址,任何操作系统,最终都要通过物理地址来访问内存。若系统开启了分页机制,则在得到线性地址后需要通过分页机制转换后,才能得到物理地址。

简单来说,由逻辑地址得到物理地址过程如下:

逻辑地址 -> 分段机制转换 -> 线性地址 -> 分页机制转换 -> 物理地址

内存分配与回收

内存的分配与回收是操作系统的重要组成部分,需要解决的核心问题包括:

1.操作系统应保证应用程序的硬件无关性,硬件差异不能体现在应用程序上

2.内存划分的区域、分配粒度、最小单位,管理区分已使用和未使用的内存,回收等等

3.优化内存碎片,考虑整体机制的高效性

mmap

mmap(Memory Map) 可以将某个设备或文件映射到应用进程的内存空间中,这样应用程序访问这块内存,相当于直接对设备/文件读写,不再需要 read、write 等 IO 操作。

mmap 函数如下:

//映射成功返回0,否则返回错误码 void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); addr:指文件/设备应该映射到进程空间的哪个起始地址 len:指被映射到进程空间的内存块大小 prot:指定被映射内存的访问权限,包括 PROT_READ(可读)、PROT_WRITE(可写) 等 flags:指定程序对内存块所做改变造成的影响,包括 MAP_SHARED(保存到文件) 等 fd:被映射到进程空间的文件描述符 offset:指定从文件的哪一部分开始映射 mmap 可用于跨进程通信,Linux Kernel 和 Android 中就频繁的用到了这个函数,比如 Android 的 Binder 驱动,下面分析 MemoryFile 原理时还会提到这个函数。

Copy on Write

image

以上为Android的开发内存管理解析;更多Android开发的技术进阶可参考《Android核心技术手册》点击可前往。

文末

Android的内存管理方式:

Android采取了一种有别于Linux的进程管理策略,有别于Linux的在进程活动停止后就结束该进程,Android把这些进程都保留在内存中,直到系统需要更多内存为止。这些保留在内存中的进程通常情况下不会影响整体系统的运行速度,并且当用户再次激活这些进程时,提升了进程的启动速度。

那Android什么时候结束进程?结束哪个进程呢?之前普遍的认识是Android是依据一个名为LRU(last recently used 最近使用过的程序)列表,将程序进行排序,并结束最早的进程。其实安卓的内存管理机制是这样的,如下:

  1. 系统会对进程的重要性进行评估,并将重要性以“oom_adj”这个数值表示出来,赋予各个进程;(系统会根据“oom_adj”来判断需要结束哪些进程,一般来说,“oom_adj”的值越大,该进程被系统选中终止的可能就越高)
  2. 前台程序的“oom_adj”值为0,这意味着它不会被系统终止,一旦它不可访问后,会获得个更高的“oom_adj”,我们推测“oom_adj”的值是根据软件在LRU列表中的位置所决定的;
  3. Android不同于Linux,有一套自己独特的进程管理模块,这个模块有更强的可定制性,可根据“oom_adj”值的范围来决定进程管理策略,比如可以设定“当内存小于X时,结束“oom_adj”大于Y的进程”。这给了进程管理脚本的编写以更多的选择。
上一篇 下一篇

猜你喜欢

热点阅读