Android Memory management

本文详细解析了AndroidRuntime(ART)和Dalvik如何通过页和内存映射管理内存,介绍了垃圾回收机制及其对性能的影响,以及共享内存的实现方式。同时,文章探讨了Android设备上RAM、ZRAM和Storage的不同作用,分析了低内存管理机制,如kernelswapdaemon和lowmemorykiller的作用原理。最后,文中提供了计算App内存占用的多种指标,并提出了减少内存使用的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://developer.android.com/topic/performance/memory-overview

Android Runtime(ART)和Dalvik使用页(paging)和内存映射(memory-mapping)来管理内存。一个App修改过的内存(通过分配新对象或touching mmapped pages)都会留在RAM中,不能paged out。 释放内存的唯一方式是通过释放App持有的对象引用,让garbage collector回收。唯一的例外是映射的文件如果没有被修改过,那么系统可以使用;比如code, 如果想使用那块内存,可以page out。

垃圾回收:找到不会再使用的数据对象,回收被这些对象占用的内存资源。
Android的垃圾回收机制是基于"代(generation)".
垃圾回收的时长取决于回收的是哪个“代”以及在每个“代”中存活的对象数量。
系统会有一套运行机制来决定什么时候进行垃圾回收,一般不建议在code中触发垃圾回收。
执行垃圾回收可能会影响App的性能,因为系统在执行垃圾回收时会停止进程的运行,如果垃圾回收时,进程正在处理比较敏感的任务,比如渲染动画或者播放音乐,那么任务处理的时间就会被拉长,导致渲染不流畅等。

低质量的代码可能会导致垃圾回收频繁发生,或者垃圾回收花费的时间比较长。


共享内存的方式:

  • App进程是从Zygote进程fork来的。系统启动的时候会启动Zygote进程,并且加载了common framework code和resources(比如Activity的主题等)(Zygote进程会启动system_server进程)。
    启动新App进程的时候系统会fork Zygote进程,然后加载App的code和资源到新进程中。这种机制也就允许新启动的App进程访问为framework code和resources分配的大部分内存页。

  • 大部分的static data是被映射到进程中。这个机制允许数据可以在进程间共享,并且也允许在需要的时候可以将这些内存数据页另作它用(page out)。
    Staic data包括:

    • Dalvik code:放在pre-linked .odex文件中直接映射。
    • app resources:把resources table设计成可以通过对齐apk的zip条目映射的结构。
    • 传统的项目元素,比如**".so"**文件中的native code。
  • 在很多方面,Android使用明确分配好的共享内存区域来作为App进程间共用的动态内存。例如,window surfaces在App和surface compositor之间使用共享内存; cursor buffers在content providers 和client之间使用共享内存。


分配和回收App内存
Dalvik heap(logic heap)的大小受限于App进程的虚拟内存空间,也就是说逻辑heap大小可以增加,但是会受限于system为App限定的上限。

逻辑heap的大小和heap使用的物理内存的大小是不同的,这点很好理解,就像逻辑内存不同于物理内存。

Android 不会整理heap中的碎片,但是当heap末端有没有使用的空间时,Android可以缩小逻辑heap的空间。system可以减少heap所使用的物理内存。
垃圾回收之后,Dalvik会遍历heap,将未使用的page通过madvise() 函数归还给kernel。
所以对于大块内存,成对的allocation和deallocation大致可以回收所有使用的物理内存,但是对于small allocation,内存回收效率相比会低一些,因为通过small allocation分配的page可能会因为share的原因而没有释放掉。

least-recently used :LRU cache中的进程会被杀掉。


Android platform的观念是“free memory is wasted memory”, 应该尽量提高内存的使用率,所以Android仍将那些被用户关掉的应用留在内存中,以便当用户再次使用时能更快的启动,达到快速切换的效果。因为这个原因,Android device经常在空闲内存比较低的情况下运行。

Android设备包含三种不同类型的存储空间:RAM,ZRAM和storage
在这里插入图片描述

  • RAM是速度最快的存储空间,但是往往容量有限;高端设备往往有大容量的RAM。
  • zRAM是RAM中用于swap的一部分空间;所有存入zRAM的数据都要压缩,取出时再解压缩。这部分空间的容量是可以改变的,设备生产商业可以设置上限。
  • Storage用于存储永久性的数据,比如文件以及app和library所使用的code等。在Android上Storage不会被用做swap space(这点不同于其他基于linux实现的平台),因为频繁的读写会磨损存储介质,降低它的使用寿命。

RAM是使用“页”来管理的,“页”的大小一般是4KB,正在使用的“页”可以做以下分类:

  • 已缓存的内存空间(Cached memory)–这类内存空间在Storage上有对应的文件,比如code或memory-mapped 文件。
    这类空间又分为两类:

    • 私有(Private)— 进程私有空间

      • 未修改过的空间(Clean)— 将Storage中的file 复制到内存后,未做过修改;这部分空间可以被kswapd 删除,来增加free memory的空间。
      • 已修改过的空间(Dirty)— 将Storage中的file 复制到内存后,已做过修改;kswapd可以将这部分空间中的数据压缩然后移动到zRAM中,来增加free memory的空间。
    • 共享(Shared)— 多个进程共用空间

      • 未修改过的空间(Clean)— 将Storage中的file 复制到内存后,未做过修改;这部分空间可以被kswapd 删除,来增加free memory的空间。
      • 已修改过的空间(Dirty)— 将Storage中的file 复制到内存后,已做过修改;kswapd可以将这部分空间中的数据写回到Storage中,来增加free memory的空间,也可以使用msync()和munmap()。
  • 匿名内存空间(Anonymous memory)— 这类内存空间在Storage中没有对应文件备份,比如通过mmap()分配的内存空间(使用MAP_ANONYMOUS flag)。

    • 已修改过的空间(Dirty) — 这类空间可以被kswapd压缩移动到zRAM中,来增加free memory空间。

Low memory management

Android有两个主要机制来处理低内存的情形:

  • kernel swap daemon
  • low memory killer
kernel swap daemon

kernel swap daemon是linux内核中的守护进程,可以将已使用的内存空间转换为空闲内存空间(free memory)。当设备中的free memory空间处于低水平值时,kswapd就会变为活跃状态,Linux为free memory设置了“Low”和“High”两个阈值,当free memory低于“Low”阈值时,kswapd就会回收内存,当free memory高于“High”阈值时,kswapd就会停止回收内存。
kswapd回收 clean page时,可以直接删除它们,因为它们在storage上有备份,需要时可以重新加载。
在这里插入图片描述
kswapd回收 private dirty page和anonymous dirty page时,可以将它们压缩移动到zRAM中;如果有进程想访问zRAM中的页,这个“页”会被解压缩然后移动到RAM中;如果和zRAM中压缩的“页”关联的进程被杀死了,那么这个“页”也会被从zRAM中删掉。
在这里插入图片描述

Low-memory killer

很多时候kswapd不能释放足够多的内存空间,在这种情形下,system会通过“onTrimMemory()”来通知App减少内存空间的分配,但是如果仍然得不到足够多的内存,那么system会通过low-memory killer来杀死进程释放内存空间。
LMK会使用“oom_adj_score”来为运行中的进程评分,得分高的进程会先被杀掉。
下面表格中列出的App,LMK的评分是从高到低,对应进程的优先级是从低到高。

Background appsBackground apps是之前运行,但是现在不是active状态,LMK会首先kill background apps中oom_adi_score最高的app
Previous appPrevious app是latest used background app,因为用户最有可能切回来,所以它比其他background app有更高的优先级
Home appHome app就是launcher app,如果被kill掉,那么背景墙纸会消失
Services被App启动的Services
Perceptible appsPerceptible apps不在前端,但是可以被用户觉察到,比如播放misuc的app
Foreground app用户正在使用的App,如果被kill掉,用户体验会很差,用户可能会觉得设备出了什么问题
Persistent(services)对设备来说persistent的app或service都是核心服务,比如telephony和wifi
System系统进程,如果被kill,那么设备可能会reboot
Native系统进程使用的low-level 进程,比如kswapd

在Linux kernel 4.12之前,内存的监控和kill process的工作是由kernel lowmemorykiller driver完成的;从Linux kernel 4.12开始在userspace使用lmkd daemon来完成这个工作。

Android提供了很多property来供system配置lmkd,在"system/core/lmkd/README.md"中可以看到这些property。

Calculating memory footprint

在计算App使用了多少RAM时,要考虑到共享内存。多个App共同使用的service或library都可以认为是共享的内存,对于这种情况,很难去计算每个App占用了多少;所以计算App的内存占用情况有三个指标:

  • Virtual Set Size(VSS):虚拟耗用内存(包含共享库占用的内存)
  • Resident Set Size(RSS):App所使用的物理内存,所有共享内存和非共享内存。
  • Proportional Set Size(PSS)::App 所使用的物理内存,包括非共享内存和 App在其所使用的共享内存中所占用的平均数量,比如3个App共同使用了3M的共享内存,那么每个App平均占用了1M 共享内存。
  • Unique Set Size(USS):App独自使用的物理内存,不包括共享内存。

VSS>=RSS>=PSS>=USS

PSS的计算方式避免了共享内存被重复计算,所以操作系统可以使用PSS计算所有进程所使用的内存空间;但是PSS的计算时间会比较长,因为系统要确认哪些内存页内共享,又被哪些进程所共享。
RSS不区分进程独占内存和共享内存,可以用来追踪内存分配的变化。


看App使用的内存情况,要结合user case,不同的操作时,所使用的内存情况不同。
另外同一个应用在不同设备平台上所使用的内存也会不同,因为所使用的资源不同,比如图片。
当系统的memory pressure增加时,同一个应用的PSS也会有所下降。

在Android studio memory profile中可以看到三个heap:zygote heap、image heap和app heap;前两者都是在App启动时从system继承来的,能做的很少,所以要关注app heap。

  • dumpsys meminfo -a
  • showmap
  • ahat
  • debug malloc

对于App减少memory use的两个有效方法:

  • 减少java heap
  • 减少Apk的size
    从Android platform stack的角度考虑,Java对象可以导致Navie 和Graphic等其他memory category的memory use的增加。

https://www.youtube.com/watch?v=w7K0jio8afM


Linux 查看内存的命令:

  • free -m
  • 查看/proc/kcore
    ll -h /proc/kcore
  • procrank
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值