找回密码
 立即注册
查看: 690|回复: 9

Unity如何统计安卓PSS内存?

[复制链接]
发表于 2021-5-18 07:58 | 显示全部楼层 |阅读模式
接上一篇Unity游戏内存分布概览。在第三部分提到了安卓的一些内存机制,但对于详细的内存类别并没有像IOS那样讲的特别细致。
那么这篇文章作为上一篇的补充,来介绍一下安卓系统的内存详情。
另:
200+篇教程总入口,欢迎收藏:
下面开始正文。
0 安卓内存分类

先来看一下android 内存的总体结构。
进程的内存空间只是虚拟内存(也可以叫做逻辑内存),而程序运行需要实实在在的内存,也就是物理内存RAM。
在必要时,操作系统会将程序中申请的内存(虚拟内存)映射到RAM,让进程能够使用物理内存。
1 按照申请类别

    dalvik 也就是Jave Heap
      dalvik Heap Size:从Runtime totalMemory()获得,表示Heap总共的内存大小。
    dalvik Heap Alloc:Runtime totalMemory()-freeMemory()获得 ,Dalvik Heap实际分配的内存大小。 dalvik Heap Free:从Runtime freeMemory()获得,Dalvik Heap剩余的内存大小。
一般来说Heap Size约等于Heap Alloc + Heap Free。我们进行内存标准判定的时候应该以Heap Size为准,相当于是Unity的“Reserved”概念。
    Native 也就是 C/C++层
      Native Heap Size:从mallinfo usmblks获得,代表最大总共分配空间。
    Native Heap Alloc:从mallinfo uorblks获得,总共分配空间。 Native Heap Free:从mallinfo fordblks获得,代表总共剩余空间。
和dalvik Heap Size一样,Native Heap Size也是约等于Native Heap Alloc + Native Heap Free。而mallinfo是一个C库, mallinfo 函数提供了各种各样的通过C的malloc()函数分配的内存的统计信息。
    Other 除去以上两种方式的所有其他类别
安卓内部类型常见的外部叫法
OTHER_DALVIK_OTHERDalvik Other
OTHER_STACKStack
OTHER_CURSORCursor
OTHER_ASHMEMAshmem
OTHER_GL_DEVGfx dev
OTHER_UNKNOWN_DEVOther dev
OTHER_SO.so mmap
OTHER_JAR.jar mmap
OTHER_APK.apk mmap
OTHER_TTF.ttf mmap
OTHER_DEX.dex mmap
OTHER_OAT.oat mmap
OTHER_ART.art mmap
OTHER_UNKNOWN_MAPOther mmap
OTHER_GRAPHICSEGL mtrack
OTHER_GLGL mtrack
OTHER_OTHER_MEMTRACKOther mtrack
OTHER_DALVIK_NORMAL.Heap
OTHER_DALVIK_LARGE.LOS
OTHER_DALVIK_ZYGOTE.Zygote
OTHER_DALVIK_NON_MOVING.NonMoving
OTHER_DALVIK_OTHER_LINEARALLOC.LinearAlloc
OTHER_DALVIK_OTHER_ACCOUNTING.GC
OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE.ZygoteJIT
OTHER_DALVIK_OTHER_APP_CODE_CACHE.AppJIT
OTHER_DALVIK_OTHER_COMPILER_METADATA.CompilerMetadata
OTHER_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE.IndirectRef
OTHER_DEX_BOOT_VDEX.Boot vdex
OTHER_DEX_APP_DEX.App dex
OTHER_DEX_APP_VDEX.App vdex
OTHER_ART_APP.App art
OTHER_ART_BOOT.Boot art

    SwappedOut  
      换出内存,没有详细类别。

2 细分类型



以上的三个大类中,每一种又分为若干个子项
3 安卓内存组合类型

到此之后,安卓的内存类型已经基本统计完毕,接下来就是平时比较关心的内存指标。这些指标大多都是从这些细分内存中组合得到的。下面的表格展示一些基本的组合信息。其中有一些条目为另一个指标的集合。
指标统计规则描述
TotalPssdalvikPss+nativePss+otherPss
+ TotalSwappedOutPss
三种Pss+换页出去的内存Pss
TotalSwappedOutPssdalvikSwappedOutPss
+ nativeSwappedOutPss + otherSwappedOutPss
三种类型的内存换页出去的PSS
TotalUssdalvikPrivateClean
+dalvikPrivateDirty + nativePrivateClean + nativePrivateDirty+ otherPrivateClean + otherPrivateDirty
三种类型的Clean和Dirty总值
TotalRssdalvikRss
+nativeRss + otherRss
三种RSS相加
TotalPrivateDirtydalvikPrivateDirty
+ nativePriateDirty + otherPrivateDirty
三种类型的Dirty内存
TotalPrivateCleandalvikPrivateClean
+ nativePrivateClean + otherPrivateClean
TotalSharedCleandalvikSharedClean
+ nativeSharedClean + otherSharedClean
TotalSwappedOutdalvikSwappedOut
+ nativeSwappedOut + otherSwappedOut
JavaHeapdalvikPrivateDirty
+OtherPrivate(OTHER_ART)
Jave的私有Dirty以及OTHER_ART相关的私有内存。
NativeHeapnativePrivateDirty
CodeOtherPrivate(OTHER_SO)
+ OtherPrivate(OTHER_JAR)+ OtherPrivate(OTHER_APK)+ OtherPrivate(OTHER_TTF)+ OtherPrivate(OTHER_DEX)+ OtherPrivate(OTHER_OAT)+OtherPrivate(OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE)+OtherPrivate(OTHER_DALVIK_OTHER_APP_CODE_CACHE)
各种代码类型的内存汇总
StackOtherPrivateDirty(OTHER_STACK)包括Java和Native的。不包括 private clean stack 因为没有这种类型
GraphicsOtherPrivate(OTHER_GL_DEV)+ OtherPrivate(OTHER_GRAPHICS)+ OtherPrivate(OTHER_GL)渲染相关的 包括 Gfx, EGL, 和 GL
不包括shared graphics。因为他们大多是针对App的buffers,可以统一归类在System分类中。
PrivateOtherTotalPrivateClean
+ TotalPrivateDirty- JavaHeap- NativeHeap- Code- Stack- Graphics
所有的Clean和Dirty之和 减掉 Java、Native、Code、Stack、Graphics
SystemTotalPss()
- TotalPrivateClean()- TotalPrivateDirty()
包含所有的shared 内存
4 内存抓取

4.1 Dump  

通过adb shell dumpsys meminfo + pid可以得到如下信息:
Applications Memory Usage (in Kilobytes):  
Uptime: 373141981 Realtime: 373141981  

** MEMINFO in pid 17109 [com.jn.performancesdk.demo] **  
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap  
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free  
                ------   ------   ------   ------   ------   ------   ------  
  Native Heap    31794    31732       12      106    56448    34740    21707  
  Dalvik Heap     5850     4980      796       40    15278     2990    12288  
Dalvik Other     1590     1584        4        0  
        Stack       40       40        0        0  
       Ashmem        6        0        0        0  
      Gfx dev     9712     9712        0        0  
    Other dev      154       88       36        0  
     .so mmap    33205      668    29280       35  
    .jar mmap     2413        0      840        0  
    .apk mmap      249        0        0        0  
    .dex mmap      588      212      376        0  
    .oat mmap      121        0        0        0  
    .art mmap     5589     4340      708       51  
   Other mmap      127       36        0        0  
   EGL mtrack    30624    30624        0        0  
    GL mtrack    36364    36364        0        0  
      Unknown    32963    32880       60        7  
        TOTAL   191628   153260    32112      239    71726    37730    33995  

App Summary  
                       Pss(KB)  
                        ------  
           Java Heap:    10028  
         Native Heap:    31732  
                Code:    31376  
               Stack:       40  
            Graphics:    76700  
       Private Other:    35496  
              System:     6256  

               TOTAL:   191628       TOTAL SWAP PSS:      239  

概览内存类型,目前大多数的安卓内存工具展示的都是以下指标。
            App Summary  
                       Pss(KB)  
                        ------  
           Java Heap:    10028  
         Native Heap:    31732  
                Code:    31376  
               Stack:       40  
            Graphics:    76700  
       Private Other:    35496  
              System:     6256  
4.2 Android Profiler



跟Dump相比,Profiler没有System 和 Private Other 项,但是多了一个Others项,并且这几个值差距比较大。以下几项的值换算完之后是几乎一致的。
           Java Heap:    10028  
         Native Heap:    31732  
                Code:    31376  
               Stack:       40  
            Graphics:    76700  
4.3 运行时统计

以上两个方式都是离线第三方的工具,那么运行时应该如何实时观测相关的指标数据呢?
4.3.1 getMemoryInfo
这个是android.os.Debug类提供的接口,通过Debug的静态方法可以获取:
其返回的指标项和Dump的一致,但由于接口权限的问题,部分内存拿的并不是很准确。实际上上面的接口使用的是这个原生函数抓取出来的。
4.3.2 getProcessMemoryInfo
通过ActivityManager来抓取。由于ActivityManager管理了所有进程的资源信息,所以需要传入ProcessId来Dump指定的应用。该接口拿到的信息和Dump的内容完全一致。通过 getProcessMemoryInfo 拿到 memoryInfo之后,可以和上面一样使用getMemoryStats来导出内存信息。
通过连续调用2组不同的接口抓取一个Demo空工程的内存信息可以看到以下结果,标黄的地方为差异较大的地方,或者说,几乎所有的差异都来自于graphics项。
但这个接口有个严重的问题,就是调用间隔是5分钟(缓存)。。。
通过Dump命令 adb shell dumpsys activity settings  可以看到该值为5分钟。
4.3.3 smaps文件
说smaps文件之前要先梳理一下常用的adb Shell 命令 是如何生效的。
当执行dumpsys命令时候,它会根据传进来的参数通过函数checkService来找到具体的service, 然后执行该service的dump方法。
memInfo对应的service可以在ActivityManagerService类下的setSystemProcess函数查看。
实际上是绑定了MemBinder类,继续跟进去可以看到dumpApplicationMemoryUsage函数。
这个函数Call了之后,会根据各种参数来设置权限详情,一个重要的影响就是dumpDetails,这个参数决定了最终接口的调用方式。
最终我们还是来到了Debug类中。
但问题来了,这个函数是一个Native函数,没有Jave层的实现。不过没关系,安卓是开源的。通过调查可以得知,该Java类对应的Native类是android_os_Debug.cpp。
https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-11.0.0_r20/core/jni/android_os_Debug.cpp
该接口也对应cpp中的 android_os_Debug_getDirtyPagesPid 函数。
查看该函数,然后就能看到“主角”登场了。
load_maps实际上就调用了smaps。
那么smaps这个东西到底是啥呢?
在Unix中,所有内容都是一个文件,即使它不是真正的文件,也会像对待文件一样进行处理和访问。这包括内核的数据结构,Linux也不例外。Linux内核允许我们通过/ proc /伪文件系统访问和读取其数据结构,Android底层基于Linux内核,进程内存信息也和Linux一致。每个进程在/ proc / [PID]中都有自己的文件夹。这里的文件和子文件夹包含有关该过程的许多有用和重要的信息,其中在proc/pid/smaps会记录某个pid的内存详细使用情况。
但该结果和和预期相差较大,无法满足正常的统计需求。下图上面是dump的值 下面是smaps抓出来的值。
其中Graphic部分统计结果不精确,原因是因为申请内存的方式不一样。
4.4 Unity的内存去哪了

Unity的内存分类很多,但如果从分配方式上来说,主要分为Native和Mono。Native是引擎底层的分配,一般来说开发者想分配也分配不了(但不合理的资源加载和接口使用可能会导致Native内存开销异常)。另一方面则是用户使用C#所分配的Mono堆内存,它包含了各种托管的内存实例。
比如一张Texture,它分为Raw Data(也就是纹理的实际数据)和C#层的class壳(方便C#层进行引用和操作),那么这张Texture的大部分内存会进入Native中,而它的“Class壳”则会进入到堆内存中,如果Texture的实例非常多,那么Mono层也会有一笔不小的开支。
那么实际上Unity的这部分内存统计,映射到安卓内存来看的话主要会分布在以下方面:
    Unity的Native内存,包括大部分的纹理,材质,动画等等数据会进入到安卓的Naitve层。 Unity的各种代码和插件代码会进入到安卓的Code层。 Unity的mono相关的内存会进入到Others层。 除此之外,安卓还会为Unity准备Graphics相关的驱动层,以及安卓自身的Java虚拟机内存。
看一组数据对比,分别是空工程的Demo和某一个大型项目在登录界面时候的内存对比情况(测试机 vivo x50)。
可以看到Code(差100M),Native(140M)和Other(340M)项的差距非常大,其他几项的的差距都没有特别明显。
再做一组数据对比,仍然是拿大型项目登录界面进行比对,其中GTL测得的PSS信息如下:
(GTL是字节出的一款游戏性能测试工具,近期应该会对外,欢迎大家关注:GTL专项测试平台)
Dump出的信息如下:
GTL工具测得的PSS信息为581.42M。而dump出来的PSS内存为604718K(换算之后得到约为590.54M),非常接近了。
但相同的界面在安卓Profiler下得到的数据为 472.2M,差距较大。
综上所述,使用getProcessMemoryInfo接口得到的数据和Dump相近,而Dump出的数据和GTL的相近,那么运行时我们可以使用getProcessMemoryInfo抓取内存数据来监控PSS内存。
4.5 Others里的数据是什么

从上面的调查结果基本可以了解各项值的意义,比如Native,graphics,Java等,但Other项非常大,且含义不明。从最开始的表里可以查到,Other就是私有数据减掉其他各项数据之和。这其中会包含Mono。
为了验证该结论,写了一个小测试,Unity空场景里放一个按钮,点击一次会增加一个400M的数组。
启动的时候数据如下:
GTL测得数据为204M,Dump出来的数据为208066K,约为203M。
当在C#层分配400M的数组之后,GTL的数据为:约增加402M的PSS内存。
Dump下的数据,Other项从30124K增加到442172K,换算后约为402M。PSS内存也由208066K增加到613626K。
以上结论可以印证Mono的内存是在安卓的Other项中。
5 真实内存的抓取方法

目前该部分仍然在尝试中,有思路我会及时更新,如果大家有思路的话,也欢迎评论告知,感谢感谢。
6 引用

native 内存和 dalvik内存

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-5-18 08:03 | 显示全部楼层
adb shell am dumpheap可以抓,但目标机要root,还有其它一系列设置。如果有相应的sym文件,可以用addr2line和objdump来解析。
发表于 2021-5-18 08:06 | 显示全部楼层
加油啊,我被这pss莫名增高烦了好多年了
发表于 2021-5-18 08:15 | 显示全部楼层
哈哈哈 好的 努力中
 楼主| 发表于 2021-5-18 08:20 | 显示全部楼层
大佬,我好崇拜你
发表于 2021-5-18 08:23 | 显示全部楼层
adb shell setprop libc.debug.malloc.options backtrace=n
这个命令要想执行成功,目标机就必须root,有的机型还可能需要替换成第三方的libmalloc。
发表于 2021-5-18 08:29 | 显示全部楼层
是的 这个方式走不过去 还在想其他办法
发表于 2021-5-18 08:37 | 显示全部楼层
如果我可以root的话,就不用这么麻烦了,直接改Global.Setting里的5分钟配置就可以了
发表于 2021-5-18 08:46 | 显示全部楼层
这个topic,我曾经研究过一个多月。尝试过很多种方法。但最后还是root治百病。开发的话,专门搞台手机root掉,或者直接买android原生机,后续开发调试上IDA也方便。
发表于 2021-5-18 08:56 | 显示全部楼层
我也弄了好久了,安卓这权限真的是麻烦。root可能是最后的方法了
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-21 02:49 , Processed in 0.098546 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表