在进行Android游戏开发过程中,内存占用总是一向不可忽视的性能指标。主要是是根据PSS的方式对整个APP的内存占用进行评估。当然,也有多种不同的方式。本文从4个角度来介绍几种不一样的内存测试。
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
1. 通过ADB获取Android内存
首先将设备通过数据线连接至PC,通过
adb shell dumpsys meminfo <package-name>
获取到如下结果。
很明显地可以获取到Total PSS以及各部分所占用的比例。我们对测试应用多次采样进行统计得到以下的曲线图。
内存总体保持在400M以上,在大多数主流的性能测试工具中也是采用该方法作为内存占用标准的。
在PerfDog中进行测试,可以获得如下图像,该图中的Memory对应的就是使用该方法获取到的Total PSS
使用UWA GOT 进行测试,可以获得如下图像。
通过查询Android的开发者文档,我们可以知道
Pss Total
包括所有 Zygote 分配(如上述 PSS 定义中所述,通过进程之间共享的内存容量来衡量)。Private Dirty
值是仅分配给您的应用堆的实际 RAM,包含了您自己的分配和任何 Zygote 分配页,这些分配页自从 Zygote 派生您的应用进程以来已被修改。
在Android系统中,zygote是一个native进程,是Android系统上所有应用进程的父进程,我们系统上app的进程都是由这个zygote分裂出来的。zygote则是由Linux系统用户空间的第一个进程——init进程,通过fork的方式创建的。
而在Android Studio的Profiler中,也是采用的该方法进行内存统计
2. 通过Android API获取Android 内存
该方法主要使用的是如下代码获取
Debug.MemoryInfo[] memoryInfos = activityManager.getProcessMemoryInfo(new int[]{pid});
Log.d(TAG,"TotalPss = "+memoryInfos[0].getTotalPss());
同样的是通过Android内部方法,表面上看起来应该是要和ADB获取到的TotalPSS是一致的,但是经过试验并非如此。我们同样的对其采样,可以获取到如下的曲线:
该方法获取到的Total PSS非常平整,主要是因为在Android 10中,该值的获取更新时间较长,为5分钟一次,而这边的采样观测时间较短,因此没法显示出区别。
其中,更新时间可以通过
adb shell dumpsys activity settings
来进行查看,返回时间为300,000 ms, 即5分钟。
我们通过对其源码进行观察
public int getTotalPss() {
return dalvikPss + nativePss + otherPss + getTotalSwappedOutPss();
}
/**
* @hide Return total PSS memory usage in kB.
*/
该方法,是从Linux的内存分配角度进行计算的。
3. 通过JAVA Runtime获取分配的内存
主要是通过如下指令进行统计
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
totalMemory - freeMemory 即可表示为当前进程已使用内存
改代码统计的是实际运行过程中进展占用的内存,但是从采样曲线来看,结果与前面两种统计的大相径庭。
Total Memory的值主要保持在25M左右,应该只有指代的当前进程使用的Memory。指代的应该是JVM虚拟机所占用的内存。我们查看它的源码:
@FastNative
public native long freeMemory();
/** * Returns the total amount of memory in the Java virtual machine.
* The value returned by this method may vary over time, depending on
* the host environment.
* <p>
* Note that the amount of memory required to hold an object of any
* given type may be implementation-dependent.
*
* *@return *the total amount of memory currently available for current
* and future objects, measured in bytes.
*/
@FastNative
public native long totalMemory();
/** * Returns the maximum amount of memory that the Java virtual machine will
* attempt to use. If there is no inherent limit then the value {*@link
** java.lang.Long#MAX_VALUE} will be returned.
*
* *@return *the maximum amount of memory that the virtual machine will
* attempt to use, measured in bytes
* *@since *1.4
*/
不过也是一种比较有根据的观测方式。
4. 通过Unity Profiler获取分配的内存
对于Unity构建的游戏,可以通过如下的内置API获取分配的内存
Profiler.GetTotalAllocatedMemoryLong()
我们通过Unity的官方文档和源码可以知道,该方法统计的主要是Unity分配的内存,不包括Unity之外的内容。
/// <summary>
/// <para>The total memory allocated by the internal allocators in Unity. Unity reserves large pools of memory from the system. This function returns the amount of used memory in those pools.</para>
/// </summary>
/// <returns>
/// <para>The amount of memory allocated by Unity. This returns 0 if the Profiler is not available.</para>
/// </returns>
[NativeMethod(Name = "GetTotalAllocatedMemory")]
[StaticAccessor("GetMemoryManager()", StaticAccessorType.Dot)]
[NativeConditional("ENABLE_MEMORY_MANAGER")]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern long GetTotalAllocatedMemoryLong();
同样的,对同一个测试程序进行多次取样,画出如下的曲线图,可以得知该内存值主要集中在138M左右。
总结:
对于以上四种内存分配数值的参考,可以让我们更好地对应用做出更好的性能评价,而结合不同的性能指标可以定位游戏开发过程中导致内存过高的具体问题是在哪个地方。对于游戏开发初学者可以简单地参考第一种内存分配值。
参考:
https://www.zhihu.com/question/67399411
https://developer.android.com/studio/command-line/dumpsys
https://docs.unity3d.com/ScriptReference/Profiling.Profiler.GetTotalAllocatedMemoryLong.html