《全民K歌内存篇1——线上监控与综合治理》
《全民K歌内存篇2——虚拟内存浅析》
《全民K歌内存篇3——native内存分析与监控》

一、简介

在多任务操作系统中,每个进程都拥有独立的虚拟地址空间,通过虚拟地址进行内存访问主要具备以下几点优势:

  1. 进程可使用连续的地址空间来访问不连续的物理内存,内存管理方面得到了简化。

  2. 实现进程与物理内存的隔离,对各个进程的内存数据起到了保护的作用。

  3. 程序可使用远大于可用物理内存的地址空间,虚拟地址在读写前不占用实际的物理内存,并为内存与磁盘的交换提供了便利。

Android系统历经dlmallocjemallocscudo等不同的内存分配器,其差异主要在内存管理的策略方面,而实际的分配与释放都是通过brk/sbrk来改变数据段大小,又或通过mmap/munmap进行匿名映射。通过malloc调用,应用程序分配到的是虚拟地址,当访问这一内存块并发生缺页中断时,才会去分配物理内存。

分页也是内存管理的一种手段,把内存划分一小块一小块的连续空间,每一块我们称之为页,页面是内存分配的最小单位,其大小一般为4KB。对于虚拟内存来说,页面共有以下几个状态:

Not Present:页面分配后未映射到物理内存;又或是作为干净页即将被内核清除Resident:当页面映射到物理内存后,需常驻于内存中,根据其内容是否存在文件备份,可划分为两种类型:Clean(干净页):仅适用于文件映射,加载到内存后不曾被更改,当内存不足时可由内核进行清除Dirty(脏页):匿名映射(不存在文件备份)或页面内容与磁盘不同。这种情况下无法由内核进行清除,因为会导致数据丢失,但可由Swapped机制进行交换处理Swapped:脏页可被交换到磁盘上,当再次发生缺页中断时才被重新加载到内存;在Android中表示通过ZRAM进行了压缩,但仍会占用部分内存

二、地址空间大小

2.1 32位的地址空间

ARM 32位的CPU架构可使用的地址空间大小为2^32=4GB,并需要保留一部分给内核使用。在Linux实现里,提供了三种虚拟地址空间分配的参数:VMSPLIT_3GVMSPLIT_2G、 VMSPLIT_1G,代表用户态可访问的虚拟地址空间大小,如下:

其中,默认的参数为VMSPLIT_3G,也就是用户态可使用3GB的低地址,剩下的1GB高地址分配给内核。

2.2 64位的地址空间

ARM 64位的CPU架构虽然拥有64位的地址空间,但并不代表我们可以全部使用。一方面是因为64位可提供地址空间实在是太大了,在未来的一段时间内都用不上那么多的地址空间;另一方面,更多的地址空间同时也意味着需要通过更加复杂的页表来进行管理。于是,ARM采取了折中的策略:在指令集方面允许使用完整的64位地址空间,为后期的扩展提供了充分的支持,而当前的CPU只使用部分地址。

通过ARM架构的官方文档,可以看到所有的Armv8-A实现都支持了48位的虚拟地址空间,即2^48=256TB,对52位的支持则只是一个可选项,大多芯片都未支持且受页面大小限制。Armv8-A共支持4KB16KB64KB三种页面大小,在64KB页面大小的前提下才可以使用52位地址,其它情况下则依然只有48位。

Android默认的页面大小配置为CONFIG_ARM64_4K_PAGES,即4KB,使用48位的地址空间需要4级页表来支持。默认的虚拟地址的长度配置为CONFIG_ARM64_VA_BITS=39,并使用3级页表CONFIG_PGTABLE_LEVELS=3进行管理,故Android的64位应用可使用的地址空间一般为2^39=512GB

Linux的内核文档也给出了这样的一些数据:

/**  * Armv8-A的虚拟地址空间分布  **/ Start                 End                    Size         Use ------------------------------------------------------------------- // 4KB页大小 + 4级页表 0000000000000000      0000ffffffffffff       256TB        user ffff000000000000      ffffffffffffffff       256TB        kernel
 // 4KB页大小 + 3级页表 0000000000000000      0000007fffffffff       512GB        user ffffff8000000000      ffffffffffffffff       512GB        kernel

2.3  64位架构上运行的32位应用

上面分别讨论了32位及64位架构下可用的虚拟地址空间,而当32位应用在64位的构架上运行时,情况会略有不同,用户态可独占低地址的32位空间,共4GB,而内核依然可以使用512GB的高地址,如下:

--------------------> +--------------------------+ffff:ffff:ffff:ffff   |                          |                                    |                          |                      |     Kernel (512G)        |                       |                          |ffff:ff80:0000:0000   |                          |--------------------> +--------------------------+ffff:ff7f:ffff:ffff   |//|                      |//|                      |//|                      z  z                      |//|                      |//|0000:0001:0000:0000   |//|--------------------> +--------------------------+0000:0000:ffff:ffff   |                          |                      |       User (4G)          |0000:0000:0000:0000   |                          |--------------------> +--------------------------+

总的来说,大多数据情况下,32位应用可独享4GB虚拟地址空间,64位应用则有512GB

2.4 内存分配的短板

进程的内存分配受虚拟内存及物理内存大小的限制,除此之外,系统对虚拟内存的区域数量也有所限制(虚线圈中的部分),可通过/proc/sys/vm/max_map_count进行查看,默认值为65536

进程使用的虚拟内存往往远大于物理内存,在32位应用中,受虚拟内存4GB的限制。而物理内存方面,绝大多数Android手机都达到了4GB甚至是8GB以上,虽然非进程独占,但系统可通过LowMemoryKillerZRAM进行管理,满足应用使用过程中的需要,问题不大。这就是32位应用的内存问题集中在虚拟内存不足的原因。

当应用升级到64位后,虚拟内存的问题得以缓解,物理内存将可能成为新的短板。笔者在多台机器上分别自测了大量分配虚拟内存大量分配物理内存的情况:

//  大量分配虚拟内存,每次调用分配20Gvoid virtualMemoryTest(){    int sizeVm = 1 * 1024 * 1024 * 1024;    //  1GB    for(int i = 0; i < 20; i++){    //  1GB * 20        void* block = mmap(NULL, sizeVm, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);        memset(block, 1, 1);        list[index++] = block;    }}
//  大量分配物理内存,每次调用申请160Mvoid physicalMemoryTest(){    int size = 8 * 1024 * 1024;     //  8M    for (int i = 0; i < 20; i++) {  //  8M * 20        void *block = malloc(size);        memset(block, 1, size);        list[index++] = block;    }}

得到两个自测的结论:

1:虚拟内存确实可以使用到接近512GB
2:大量申请物理内存导致内存紧张时,后台应用及服务会逐一被LowMemoryKiller杀死,继续申请时,前台应用亦不能幸免,表现为闪退,但无法被Bugly捕获。

以下是一个4GB物理内存的机器,前台应用申请到3864316KB(约3.7GB)时,被系统杀掉了。

<6>[122969.200223] lowmemorykiller: Killing 'raoke.memoryApp' (21760) (tgid 21760), adj 0,<6>[122969.200223] to free 3864316kB on behalf of 'UsbFfs-worker' (25160) because<6>[122969.200223] cache 72088kB is below limit 73728kB for oom score 0<6>[122969.200223] Free memory is -35644kB above reserved.

至于区块数量,以全民K歌为例,启动并覆盖录歌、直播、歌房等主场景后,不超过8000条,估计还没那么容易超出默认的限制。

三、地址空间分布

Android的应用程序都是从zygote进程fork而来,由Copy On Write的特性可知,应用启动时拥有与zygote进程一样的虚拟地址空间。此时的地址空间主要包括zygote进程启动时为ART虚拟机分配的各种space,其布局概况如下:

ffffffff   +---------------------------+           |                           |           +---------------------------+           |          stack            |           +---------------------------+           |                           |           |                           |           z                           z           |                           |           |                           |           +---------------------------+           |   no moving space (62+M)  |           +---------------------------+           |    zygote space (1+M)     |           +---------------------------+           |        image space        |           +---------------------------+           |                           |52C00000   +---------------------------+           |                           |           |    main space 2 (512M)    |           |                           |32C00000   +---------------------------+           |                           |           |    main space 1 (512M)    |           |                           |12C00000   +---------------------------+           |                           |           |                           |00000000   +---------------------------+
  • main space

    0x12c00000开始分配共1GB的空间,用于虚拟机内部对象的管理,我们在Java层分配的内存就是使用了这部分的空间。

  • image space

    主要是系统启动相关的一些镜像文件。

  • zygote space/non moving space

    zygote进程启动后加载的一些类及其它其它资源,实现在在不同进程间共享,不参与GC

  • 其它

    还有其它一些类型的内存受ASLR策略的影响,分布较为分散

笔者通过AS创建了一个简单的demo,应用启动后,从虚拟内存大小的角度看,排名前20的内容为(单位KB):

 VmSize      PSS    Dirty Swap    Mapping ------   ------  -----  -----    ------1048576      380    380      0    [anon:dalvik-main space (region space)] 汇总 133120        0      0      0    [anon:libwebview reservation] 汇总 110144      216    216     16    [anon] 汇总  98304       16      8      0    jit-cache (deleted) 汇总  63760       92     92      0    [anon:dalvik-non moving space] 汇总  47552        0      0      0    icudt63l.dat 汇总  32768        0      0      4    [anon:dalvik-zygote-data-code-cache] 汇总  32768        0      0      8    [anon:dalvik-zygote-jit-code-cache] 汇总  29324     1232      0      0    framework.jar 汇总  24196        0      0      0    NotoSerifCJK-Regular.ttc 汇总  19856        0      0      0    NotoSansCJK-Regular.ttc 汇总  16384        0      0      0    [anon:dalvik-concurrent copying gc mark stack] 汇总  16384        8      8      0    [anon:dalvik-region space live bitmap] 汇总  16384        0      0      0    [anon:dalvik-region-space inter region ref bitmap] 汇总  16104       70      0      0    oppo-framework-res.apk 汇总  11628       20      0      4    boot-framework.oat 汇总  11264     4776   4708   1372    [anon:libc_malloc] 汇总  10496     3601     12     28    libllvm-glnext.so 汇总  10052        0      0      0    SysSans-Hans-Regular.ttf 汇总  10048       89      0      0    framework-res.apk 汇总

由此可见,应用所使用的虚拟内存大小要远大于实际使用的物理内存,主要的消耗对象为:Java堆、虚拟机相关缓存、webview及一些系统资源。在32位的应用上,内存使用不合理时,会比较容易引发因虚拟内存不足而导致的白屏或OOM等问题。

四、虚拟内存分析

4.1 smaps介绍

对于当前虚拟内存的使用状态,可通过读取/process/pid/status中的VmSize字段来得到。如果希望进一步分析,则可读取/process/pid/smaps,这一文件记录了进程中所有的虚拟内存分配情况。通过一些命令行工具还可整理提取出其中的关键信息,比如pmap可获取到的数据是这样的(篇幅有限,只选取了部分内容进行展示):

Address           Kbytes     PSS   Dirty    Swap  Mode   Mapping0000000012c00000     256       4       4       0 rw---   [anon:dalvik-main space (region space)]000000001fb40000  836352       0       0       0 rw---   [anon:dalvik-main space (region space)]0000000070b9c000    1964     771     744      56 rw---   boot.art0000000071b2f000       8       0       0       0 r--s-   boot.vdex0000000071b31000       4       0       0       0 r----   boot.oat0000000072ae4000    1776     616     588     792 rw---   [anon:dalvik-zygote space]0000000072ca0000      12      12      12       0 rw---   [anon:dalvik-non moving space]00000000bde80000     512     136     136       0 rw---   [anon:libc_malloc]00000000d00a8000     260      43       0       0 r--s-   base.apk00000000e30dd000     888     187       0       0 r----   framework.jar00000000e5df4000       4       4       4       0 rw-s-   kgsl-3d000000000e6f62000     168       2       0       0 r----   libmedia.so00000000e7130000      32       0       0       0 r--s-   NotoSerifLao-Bold.ttf00000000f571b000       4       4       4       0 rw---   linker00000000f5729000   32768       4       0       0 r--s-   jit-cache (deleted)00000000ff37d000    8188      44      44       0 rw---   [stack]00000000ffff0000       4       0       0       0 r-x--   [vectors]----------------  ------  ------  ------  ------total            1988112   22318   11600    7032

主要包括Kbytes(虚拟内存)PSS(物理内存含公摊)Dirty(脏页)Mapping(映射文件或匿名内存)等字段。mapping指示了这一块内存的去向,通过文件映射的内存都能在这里找到详细的记录,比如加载的artapksottf等。举个例子,当我们通过adb shell dumpsys meminfo pid发现某个场景so类型的内存增量不合理时,只要对比场景前后的/proc/pid/smaps文件就可以轻易地知道是加载了那些so库,它们分别占用了多大的内存空间。

而另一部分以anon开头的则代表匿名映射,不具备文件备份,包括由java虚拟机或内存分配器来管理的空间,mapping中只能看到[anon:dalvik-xxx][anon:libc_malloc]这样的信息,无法具体分析,但可结合Java内存快照或native hook的工具进行进一步的分析。

4.2 内存增量分析

全民K歌的直播间业务中,观众首次进退房后会存在一定的内存增量,我们需要分析这一增量是否合理。在此,我们以PSS为例(分析VSS增量也可使用同样的办法),通过以下几个步骤进行分析:

1. 通过pmap命令获取进房前及退房后的虚拟内存映射并保存下来

//    进房前的内存映射,命名为 map1.txtAddress            Kbytes     PSS   Dirty    Swap  Mode  Mapping0000000012c00000    5888    2248    2248       0 rw---    [anon:dalvik-main space (region space)]00000000131c0000    7936       0       0       0 -----    [anon:dalvik-main space (region space)]0000000013980000    9728    8340    8340       0 rw---    [anon:dalvik-main space (region space)]0000000014300000   29952       0       0       0 -----    [anon:dalvik-main space (region space)]......00000000ff014000       4       0       0       0 -----    [anon]00000000ff015000    8188      84      84       0 rw---    [stack]00000000ffff0000       4       0       0       0 r-x--    [vectors]----------------  ------  ------  ------  ------total            2444128  259868  182884   11240
//     退房后的内存映射,命名为 map2.txtAddress            Kbytes     PSS   Dirty    Swap  Mode  Mapping0000000012c00000    4864    1988    1988       0 rw---    [anon:dalvik-main space (region space)]00000000130c0000   10496       0       0       0 -----    [anon:dalvik-main space (region space)]0000000013b00000    7936    7400    7400       0 rw---    [anon:dalvik-main space (region space)]00000000142c0000     256       0       0       0 -----    [anon:dalvik-main space (region space)]0000000014300000    3840    3772    3772       0 rw---    [anon:dalvik-main space (region space)]......00000000effc4000       8       8       8       0 rw---    [anon]00000000ff014000       4       0       0       0 -----    [anon]00000000ff015000    8188      84      84       0 rw---    [stack]00000000ffff0000       4       0       0       0 r-x--    [vectors]----------------  ------  ------  ------  ------total            2495076  289399  197128   10928

通过最后一行total的对比,可知PSS的增量为289399-259868 ≈ 30M

2. 两个文件存在大量的重复内容,先通过脚本删除重复项,再将同一文件内相同mapping的内容合并为一条,分别得到文件merge1.txtmerge2.txt,如步骤1的map1.txt展示了4条[anon:dalvik-main space (region space)],合并后是这样的:

// 通过mapping合并后的数据示意Kbytes     PSS   Dirty    Swap  Mode  Mapping 53504   10588   10588       0 rw---  [anon:dalvik-main space (region space)]

3. 对merge1.txtmerge2.txt中相同的mapping内容进行增量计算,并分类汇总。

private fun analysisLineType(lines: ArrayList<Line>){    lines.forEach {        it.type = when {            it.mapping.startsWith("[anon:dalvik-alloc space") ||            it.mapping.startsWith("[anon:dalvik-main space") ||            it.mapping.startsWith("[anon:dalvik-large object space") ||            it.mapping.startsWith("[anon:dalvik-non moving space") ||            it.mapping.startsWith("[anon:dalvik-zygote space") ||            it.mapping.startsWith("[anon:dalvik-free list large object space") -> {                "[1]dalvik"            }            it.mapping.endsWith(".so") -> {                "[8].so"            }            it.mapping.endsWith(".art") ||            it.mapping.endsWith(".art]") -> {                "[e].art"            }            it.mapping == "[anon]" -> {                "[f]other-mmap"            }            ......    //    省略其它分支        }    }}

4. 经过以上各个步骤的处理后,得到的结果是这样的:

各个数据代表着内存的变化,大于0代表内存增加,小于0则是释放了部分的内存。这一结果可分析各个类型的内存增量并罗列变化的具体内容。

以截图为例,加载libYTCommon.so使用了304KB的虚拟内存,且已全部映射到物理内存,其中的288KB是干净页,没有修改过的。在这个案例中,这一加载是不合理的,因为它是优图用于图像处理的SO库,观看直播其实是不需要用到的。

带有dalvik-关键字的则与JAVA虚拟机有关,如dalvik-main space(region space)的虚拟内存增量为0,这是因为应用启动时就预分配了大量的空间为其所用,当我们在JAVA层分配对象时,会反映在PSS上,需要结合Java dump进一步分析。

五、总结

虚拟内存为应用开发及系统的进程管理提供了极大的便利。但在32位应用中,因受地址空间的限制,容易成为内存分配的短板,从而引发Crash或白屏。本文主要对虚拟内存的大小、布局及内容进行了简要的分析,望抛砖引玉。

转自:全民K歌内存篇2——虚拟内存浅析

全民K歌内存篇2——虚拟内存浅析相关推荐

  1. 见招拆招,敏捷实战:揭秘全民K歌背后的音乐黑科技

    随着数字音乐的不断发展,线上 K 歌越来越流行.移动 K 歌从用户深层情感需求与消费场景出发,满足了新生代用户表达情绪.抒发个性的需求.而全民 K 歌作为 K 歌平台中的"标杆", ...

  2. 全民K歌推荐后台架构

    分享嘉宾:davidwwang 腾讯音乐 | 基础开发组副组长 编辑整理:梁尔舒 出品平台:DataFunTalk 导读:首先介绍一下我们业务背景,腾讯音乐集团,于2018年是从腾讯拆分独立上市,目前 ...

  3. android 仿全民k歌 线谱乐谱音高图

    全民k歌大家都不陌生吧,在嗨歌时有一个线谱样式的动画效果是不是很吸引人呢. 效果似乎很复杂,感觉上非自定义view莫属了,然而如何处理滑动.如何处理颜色.如何处理多段线条.如何处理数据变化...... ...

  4. 全民K歌直播推荐算法实践

    导读:直播是社交娱乐app的综合性变现工具,如何培养用户的心智,高效的建立用户和主播的多种连接 ( 点击.观看.关注.常看.常打赏 ) 是直播生态的重要问题之一.为了解决这个问题,各大平台所使用的方法 ...

  5. 腾讯音乐:全民K歌推荐系统架构及粗排设计

    编辑整理:张振.于洋 导读:腾讯音乐娱乐集团 ( TME ) 目前有四大移动音乐产品:QQ音乐.酷狗音乐.酷我音乐和全民K歌,总月活超8亿.其中,全民K歌与其他三款产品有明显的差异,具体表现如下:以唱 ...

  6. 复盘 | 听全民K歌体验设计师聊聊歌房项目完整设计历程

    PMCAFF(www.pmcaff.com):互联网产品社区,是百度,腾讯,阿里等产品经理的学习交流平台.定期出品深度产品观察,互联产品研究首选. 外包大师(www.waibaodashi.com): ...

  7. k歌打分原理php,全民K歌修音教程:想要获得SSS评分其实很简单

    很多朋友应该都用过全民K歌这个软件,它是一款可以智能打分.和好友互动的K歌软件.里面经常有很多大神,唱歌可以和歌手媲美,但是为什么我们每次录的歌听着总是没有自己想要的效果呢?大家看完这篇修音教程就能找 ...

  8. 怎样把k歌作品发到html里,如何将自己的原创歌曲上传入库到全民K歌

    使用全民K歌唱歌的人越来越多,流量也越来越大,所以,歌曲的曝光量也就随之增加,只要你的原创歌曲旋律可以,一定会有很多人进行翻唱和打榜.每天看着越来越多的人唱自己的作品时,荣誉感爆棚.那么我们自己的歌曲 ...

  9. 怎样把音乐保存到计算机,全民K歌怎么将歌曲保存到电脑

    首先我以全民K歌为例,把作品分享到我的QQ好友,由此会得到一个链接 然后我们选择刚才发送的好友,打开这个链接 打开之后这里会有缓冲进度,等待缓冲完成 然后我们打开浏览器的"internet选 ...

最新文章

  1. 【AI】caffe使用步骤(四):训练和预测
  2. php实现项目的日志记录功能,tp5框架使用composer实现日志记录功能示例
  3. SetAutoResizeMode
  4. 根据url获取html源码,通过URL访问和获取html源代码
  5. php validator classes
  6. python金融数据分析电子版_python 金融大数据分析 pdf
  7. 对项目和产品中坎坎坷坷的一些感悟
  8. 在线文档转word文档
  9. Windows Server 2013 安装zune 4.8中文版
  10. PMP-5.项目范围管理-需求跟踪矩阵
  11. 水库河道应急广播系统解决方案
  12. nginx 返回动态Html,Nginx动态、静态分离,Nginx配置中做适配
  13. redis中以目录形式存储和读取数据
  14. 阿里首席风险官郑俊芳:安全是我们的生命线,将时刻保持敬畏心
  15. Nodejs的开发工具Nide的安装过程
  16. 平板时代即将到来,写在即将发布Win8 Surface平板和iPad Mini之际
  17. 网络故障的技术一些东东
  18. 洛谷 Cantor 表
  19. 下划线命名法 vs 驼峰命名法
  20. centos,php,apache,nginx,vim命令相关知识和命令记录

热门文章

  1. 服务器光口位置,linu服务器光口配置ip
  2. 【Unity】UGUI超级简单的摇杆制作,摇杆控制物体移动
  3. 全景图转换为天空盒图--再尝试
  4. 史上最全的Android面试题集锦,Android面试题及解析
  5. 鸿蒙麒麟华为手机多少钱一部,华为P50真机外观曝光!搭载麒麟9000,首款鸿蒙手机...
  6. java游戏孙悟空上网吧_王者荣耀:李白和孙悟空在网吧玩游戏,结果……
  7. python装饰器与闭包_python闭包与装饰器
  8. UE4从零开始的卡通渲染——阴影篇(二)
  9. 16进制转ASCii码
  10. 有效记忆nginx正反代理定义