在看这篇文章之前,需要Linux内存管理基础,推荐先学习一些Linux内存管理基础知识点,这里我们在做一些应用层的优化工作,但从底层理解一些原理性的知识点,对我们有较大帮助。
主要讨论四点内容:
Android 运行时 (ART) 和 Dalvik 虚拟机使用分页和内存映射来管理内存。这意味着应用修改的任何内存,无论修改的方式是分配新对象还是轻触内存映射的页面,都会一直驻留在 RAM 中,并且无法换出。要从应用中释放内存,只能释放应用保留的对象引用,使内存可供垃圾回收器回收。这种情况有一个例外:对于任何未经修改的内存映射文件(如代码),如果系统想要在其他位置使用其内存,可将其从 RAM 中换出。
页是一种内存管理技术,它允许进程的物理内存不连续。它通过在称为页面(Page)的相同大小的块中分配内存来消除碎片问题,是目前比较优秀的内存管理技术。分页将物理内存划分为多个大小相等的块,称为帧(Frame)。并将进程的逻辑内存空间也划分为大小相等的块,称为页面(Page),通过页表(Page Table)用于查找此刻存储特定页面的帧,Android 使用的分页稍有不一样的地方。
内存映射(mmap)是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和应用程序进程虚拟地址空间中一段虚拟地址的一一映射关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上。应用程序处理映射部分如同访问主存。
Android内存管理方式,通常,Android设备的内存分为很多页(Page),每页大约4 KB。
Image source: Google I/O 18
下面看看内存压力对设备的影响。显示了一段时间内的内存使用情况,当 Cached Pages 低于 LMK 阈值时,将会触发低内存杀死机制。
所以,你可以想象 LMK 在低内存手机上的情景:
Linux Kernel 将会持续跟踪每个进程使用的页(Pages),所以只要对进程使用的 Pages 进行统计
有些 Pages 是进程间共享的:
那么问题来了?如何处理此共享内存。是由应用程序负责共享内存,还是由Google Play服务负责此共享内存?有几种不同的方法可用于处理这些情况:
但是总的来说,很难找到应用程序是否需要共享内存。因此,我们使用“比例集大小”方法。PSS避免了过多计算或低估了共享页面对设备的总体影响。
通过上面我们知道,Android使用 分页 和 内存映射 来构建虚拟内存,同时使用** 垃圾回收器** 来 回收内存 ,值得注意的是, 应用修改的任何内存 ,无论修改的方式是分配新对象还是轻触内存映射的页面, 都会一直驻留在 RAM 中 ,并且不会换出到磁盘。使用 LowMemoryKiller (LMK)在低内存的时候来 杀死进程 释放更多内存,使用PSS来评估应用占用的内存,
下面是运行时内存占用介绍:
您应用中的 Dalvik 分配所占用的 RAM。Pss Total 包括所有 Zygote 分配(如上述 PSS 定义中所述,通过进程之间共享的内存容量来衡量)。Private Dirty 值是仅分配给您的应用堆的实际 RAM,包含了您自己的分配和任何 Zygote 分配页,这些分配页在从 Zygote 派生您的应用进程以来已被修改。
.so mmap 和 .dex mmap:
映射的 .so(原生)和 .dex(Dalvik 或 ART)代码占用的 RAM。Pss Total 值包括应用之间共享的平台代码;Private Clean 是您的应用自己的代码。通常,实际映射的内存容量要大得多。此处的 RAM 只是应用已执行的代码当前需要占用的 RAM。不过,.so mmap 具有较大容量的私有脏 RAM,这是因为在将其加载到最终地址时对原生代码进行了修复。
.oat mmap:
这是代码映像占用的 RAM 容量,根据由多个应用共用的预加载类计算。此映像在所有应用之间共享,不受特定应用影响。
.art mmap:
这是堆映像占用的 RAM 容量,根据由多个应用共用的预加载类计算。此映像在所有应用之间共享,不受特定应用影响。尽管 ART 映像包含 Object 实例,但它不会计入您的堆占用空间。
以上Code占用内存**,我们可以通过“adb shell cat /proce/PID/smaps”直接将这个虚拟文件的信息打印在控制台上,它实际上是应用的用户空间**地址的内存分配表,记录了应用分配的每一块内存的地址,类别,大小等信息。
但是应用所使用的全部内存里面,有一些内存块是不映射到进程的用户空间地址空间的(主要是GPU所使用的内存),这些内存块的信息在smaps里面无法找到,所以在Android 4.4里面新增了一个memtrack的HAL模块由SoC厂商实现,如果SoC厂商实现了memtrack模块,meminfo则可以通过libmemtrack的调用获取一些跟GPU相关的内存使用信息。
Memory Tracker(memtrack),是一个android_hardware层的库,不一样平台库的名称不一样,实现方式也有差别。
graphics memory分为五种类型数据:
以查看AOSP源码高通为例,memtrack_msm.so库作的事情很简单,根据上层传递的type读取对应节点,获取内存信息。
int msm_memtrack_get_memory(const struct memtrack_module *module, pid_t pid, int type, struct memtrack_record *records, size_t *num_records){ if (type == MEMTRACK_TYPE_GL || type == MEMTRACK_TYPE_GRAPHICS) { return kgsl_memtrack_get_memory(pid, type, records, num_records); } return -EINVAL;}MEMTRACK_TYPE_GL = GL mtrack,MEMTRACK_TYPE_GRAPHICS = EGL mstrack
gralloc分配的内存,主要是窗口系统,SurfaceView/TextureView和其他的由gralloc分配的GraphicBuffer总和。
驱动上报的GL内存使用情况。 主要是GL texture大小,GL command buffer,固定的全局驱动程序RAM开销等的总和。
理解以上内存更多详细内容,我们需要了解更多的体系内容如:
影响应用程序性能其他因素:
硬件位图仅在显存 (graphic memory) 里存储像素数据,并对图片仅在屏幕上绘制的场景做了优化。
优化Bitmap的使用、针对性及时分配,及时释放策略
代码中的某些资源和库可能会在您不知情的情况下吞噬内存。APK 的总体大小(包括第三方库或嵌入式资源)可能会影响应用的内存消耗量。
这块主要记录我们治理OutOfMemoryError 的一些优化方式与监控方案。
第一节我们学习了Android 内存管理机制的一些前置知识,内存管理技术、是如何影响我们的应用的,知道了Android 内存区域的划分及部分原理,我们就可以针对各个区域提出内存优化的思路来提高我们的内存使用效率。
最后是我们治理OutOfMemoryError 的一些优化方式与监控方案,这块通常是分析后根据实际情况制定优化方向及方案来进行的。