找回密码
 立即注册
查看: 603|回复: 0

Unity移动平台内存限制相关

[复制链接]
发表于 2021-1-27 11:50 | 显示全部楼层 |阅读模式
PSS在不同手机测试的结果是不同的

USS = 进程独占的内存
RSS = USS + 共享内存
PSS = USS + 共享内存/共享这段内存的进程数量


所以,由于加上了共享内存,PSS的数值是非常不稳定的,和手机的系统和版本都有关系。由于共享内存还需要除以进程数,它甚至还和你安装的软件,和运行时后台程序的数量相关……
因此,你家随便一台手机测试出来的PSS,和腾讯检测时测出来的PSS很可能不同,也就不能死板地拿腾讯的标准对比。
正确的做法是,准备一个固定的测试环境,然后提交一个版本到腾讯,得出两者PSS的差值。然后以后的本地测试都减去这个固定差值就行。
或者,用腾讯本家的游戏作为对比参考,比它们低就行。
腾讯的可以,凭啥你的就不行呢?


用PSS判断应用的内存占用科学吗?

当一个进程被杀掉后,释放出来的内存量是USS。
应用本身主动申请的所有内存,堆/栈/纹理数据等等都属于private dirty,也就是USS。共享内存并不是公共库这样的概念,你使用公共库,在公共库的范围创建了新的对象,那部分对象依然属于USS。
共享内存在内存分配上有点类似“静态数据”或者“代码”,应用对它只能读取而不能写入。想要写入就必须先复制一份到USS。所以按我们常规的对“内存分配”的理解,USS就是单个进程占用的内存的全部。
所以,用USS来判断一个应用的内存占用才是科学的。我认为,PSS多出来的“share dirty / progress number”部分根本毫无意义。


PSS看起来这么不科学,为啥要把它当作限制?

    安卓的任务管理器显示的应用内存占用就是PSS。并不确定系统是否是以它作为管理基准。USS在非Root的情况下,不能由外部进程获得。有可能是当初为了检测方便。很多事情并没有什么“为什么”,你只能接受。而且在孤立环境下PSS检测确实问题不大,因为同一引擎同一时期开发的游戏,PSS比USS多出来的部分“大概”是个固定值,标准相应加上这个固定值就好了。


如何获取PSS和USS?

虽然控制内存的时候大部分情况用Unity自己的Profiler就行了——
但Unity确实不会统计所有的内存占用。我们可以确定,所有额外引入的C代码库它们自己申请的非托管内存都不会计算在内,其中就包括lua。另外,平台渠道的架包它们自己申请的内存也不会计算在内。
如果只是自己控制内存的话,只看Unity的Profiler和Lua单独的内存统计差不多就够了。但是像渠道代码,语音库这类东西,虽然我们对它们无能为力,毕竟是捆绑在一起的,必须给它们挪出相应内存的空闲来,所以我们依然必须关心整个应用的内存占用(但Unity Profiler的结果同样要关心,因为它才是普适的)
而比起用adb查看,能够通过游戏界面呈现出来显然更加便利。


应用内部获得内存数据的办法是 Debug.getMemoryInfo。
Debug.MemoryInfo localMemoryInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(localMemoryInfo);
localMemoryInfo.getTotalPss();
localMemoryInfo.getTotalPrivateDirty();//USS

不编程的话,可以用adb工具查看
adb shell dumpsys meminfo 进程名(注意结果里privte dirty就是USS)


获取的实时PSS/USS可以用来做什么?

主要是Debug时用来参考。考虑到运行效率,发布版本最好关闭这个功能。
但……它也可以帮助大家确保通过平台PSS上限的检测。与其担心偶尔超过规定的PSS上限而畏手畏脚降低资源数量,不如放开手,然后在即将到达PSS上限的时候强行回收内存。实在不够的时候,甚至可以禁止新资源加载,用空白物体取代,只保证游戏逻辑可以持续下去。
虽然这里只是为了应付检测,但在实际游戏运行中,如果确实出现了内存不足的情况,这种“放弃游戏体验”的做法也不失为一个办法,毕竟总比应用崩掉要好。


如何才能避免我们的应用由于内存不足崩掉?

获取PSS/USS的数值并不能帮助我们做到这点。
——因为操作系统并不会因为一个进程占用了固定数量的内存而杀掉它(更不要说每个设备的内存总量还不一样),操作系统只会在内存严重不足,也没有后台进程可杀的时候,才会为了保证系统安全而杀掉前台进程。
所以,我们要做的就是在内存确实不够的时候设法降低内存占用,乃至牺牲体验。具体的做法就是:
    执行GC回收资源,删除缓存,删除预加载内容降低画质级别以上皆无效,拒绝新资源的加载要求用户重启应用


执行时机:
一个办法是实时获取当前系统剩余内存并处理
某著名游戏的代码
MemoryInfo:
availMem 当前系统剩余内存
threshold “低内存”的标准
lowMemory  当availMem<threshold即为true,这时候系统会开始关闭后台应用
这段逻辑也可以用来验证回收资源的成果,判断是否要使用更激进的做法。


接受系统发出的内存警告提示
安卓:
onTrimMemory(int state)
当进程在前台时,state的值可能是以下三个
TRIM_MEMORY_RUNNING_CRITICAL:内存不足(后台进程不足3个)
TRIM_MEMORY_RUNNING_LOW:内存不足(后台进程不足5个)
TRIM_MEMORY_RUNNING_MODERATE:内存不足(后台进程超过5个)
这个事件触发比较早,操作系统还可以关闭后台进程来周转。只是提醒在用户内存的紧迫程度。

onLowMemory()
在所有后台进程关闭后,同时处于lowMemory状态时触发。这时候系统已没有内存腾挪的空间。剩余内存继续变少,前台应用随时可能被关闭,以保障操作系统的正常运行(否则只能死机)
IOS:
- (void)didReceiveMemoryWarning
{
  (int)OSMemoryNotificationCurrentLevel());
}

OSMemoryNotificationCurrentLevel()的枚举:
OSMemoryNotificationLevelAny      = -1 正常
OSMemoryNotificationLevelNormal   =  0 正常
OSMemoryNotificationLevelWarning  =  1 内存不足警告
OSMemoryNotificationLevelUrgent   =  2 严重内存不足警告
OSMemoryNotificationLevelCritical =  3 闪退(所以实际上无法收到此消息)

OSMemoryNotificationLevelWarning可以认为等效onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL),只是提示出现内存不足的现象,这时候会通过关闭后台进程来获取额外内存,并不一定非要处理。
OSMemoryNotificationLevelUrgent 可以认为等效onLowMemory,表示系统已经处于非常危险的状态,前台进程随时都可能被关闭。


在Unity 5.6之后,提供了系统事件Application.lowMemory,相当于:
- iOS: [UIApplicationDelegate applicationDidReceiveMemoryWarning]
- Android: onLowMemory() and onTrimMemory(level == TRIM_MEMORY_RUNNING_CRITICAL)


并不应该无脑的执行回收逻辑

当内存出现警告时,并不代表必须要马上执行缩减内存的操作,因为这些操作都是有代价的,仅仅是频繁地GC就会导致游戏体验的极大下降,而这是有可能在不闪退的前提下避免的。
系统给出的内存不足提示本身是为App准备的,如果你在浏览网页过程中弹出了支付宝,显然,你并不希望在支付宝运行过程中把网页的进程杀掉。当内存不足,网页进程要被杀掉前,系统就会给支付宝发一个提示,希望它也能停止申请内存,以便给网页进程保活。
从App的角度,每个应用都应该尽可能共存,内存警告系统也是这样设计的。但是游戏则不同。对于玩家而言,进入游戏把后台的网页杀掉不仅没有关系,也是预期会发生的事。毕竟现在也不兴玩游戏前“清除内存加速球”之类的玩意儿了。
我进游戏前开了几个应用,我没有强关他们,然后我进游戏,游戏为了不杀掉他们却要强行缩减自己的内存降低画质让游戏变卡??
这不是自找没趣么?


不过,从实际结果来看,当到了OSMemoryNotificationLevelWarning,onTrimMemory(level == TRIM_MEMORY_RUNNING_CRITICAL),也就是Unity的Application.lowMemory的触发点的时候,应用确实已经处于较为危险的地步。虽然游戏还可以正常运行,后台进程大多已经关闭,切回桌面也需要重新启动,甚至来个电话收个短信都可能导致游戏被关闭。虽然可能不至于闪退,体验已经变得不好。
个人建议,这个提示出现后只进行对体验影响较小的处理。
如果连续出现,则加大处理的力度。
但如果连onLowMemory,OSMemoryNotificationLevelUrgent都已经被触发了,则必须不计一切代价将内存降低下去,不处理是真的要完蛋的。


此外,onLowMemory,OSMemoryNotificationLevelUrgent在内存申请较快的情况是可能不出现便直接闪退的,它们并不保险。这点在场景加载过程极易发生,所以在加载过程时对内存警告的处理就要更加保守。而在内存较为平稳的阶段,就可以稍微倦怠一些,等待onLowMemory出现后再处理。
然而这些做法其实无异于在刀尖上跳舞,可以的话,还是要尽力将内存降低下去,应用切换的体验也是有必要保证的。切出游戏便无法回来,玩家的体验并不好。


然而如果有平台的PSS限制的话……因为他们的限制往往过于求稳,出现Application.lowMemory(甚至在之前)恐怕就必须清理内存才能满足他们的要求,也就不用考虑那么多了。




参考:
解析iOS内存不足时的警告以及处理过程
IOS 内存警告 Memory warning level
应用内存优化之OnLowMemory&OnTrimMemory - 千里之行~~ - 博客园
Android查看内存使用的方式(Running services、MemoryInfo、getNativeHeapSize)
android内存性能数据获取--api篇 - CSDN博客
Android 内存分析和调优 - CSDN博客

本帖子中包含更多资源

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

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-9-20 13:27 , Processed in 0.137528 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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