Perfdog玩转内存泄漏

标签: perfdog 内存泄漏 | 发表时间:2020-06-28 19:52 | 作者:腾讯WeTest
出处:https://www.iteye.com

背景交代

最近QC同学在跑游戏的过程中发现玩的时间久了游戏会发生闪退,经过搜集信息后排除了功能性bug的

 

一.判断是否是内存泄露

拿到真机,USB连接,杀掉多余后台进程,打开Perfdog,接下来一顿操作猛如虎,Perfdog具体操作不在赘述,有关perfdog怎么使用的教程可以参考

Perfdog使用教程

 

拿到内存趋势图

使用手机

 

此图一出,基本就可以断定内存泄露了,这是正常玩游戏,游戏运行了30分钟的内存趋势图;

结论:,内存持续上升,存在内存泄露。

一个优秀的游戏通常情况内存是有上升有回落,多次运行同一个功能也不会导致内存功能持续上升;

呈现出起伏状态,比如:

知道了存在内存泄露,下面就要开始分析有可能是哪里导致的内存泄露;

 

二.分析泄露原因

一般针对unity游戏来说,内存瓶颈是资源和Mono堆内存,两部分;

以下是unity游戏程序在运行时的内存分配概况

先简单介绍下Mono,unity使用Mono机制来完成跨平台的操作,就好像JAVA使用虚拟机来完成跨平台操作一样,Mono也是一种跨平台的实现。跨平台其实现原理在于使用了叫CIL(Common Intermediate Language通用中间语言,也叫做MSIL微软中间语言)的一种代码指令集,CIL可以在任何支持CLI(通用语言基础结构)的环境中运行,就像.NET是微软对这一标准的实现,Mono则是对CLI的又一实现。由于CIL能运行在所有支持CLI的环境中,例如刚刚提到的.NET运行时以及Mono运行时,也就是说和具体的平台或者CPU无关。

一般对于unity开发的游戏来说,内存的开销都是围绕下面的三个方面:

1.资源内存的占用;

2.引擎模块自身内存占用;

3.托管堆内存占用。

 

Mono通过垃圾回收机制(GarbageCollect,简称GC)对内存进行管理,可以自动地改变堆的大小来适应你所需要的内存,并且是可以适时地调用垃圾回收(GarbageCollection)操作来释放已经不需要的内存。也就是说Mono会自动释放一些内存,但要注意的是GC释放的内存只会留给mono使用,并不会交还给操作系统,因此mono堆内存是只增不减的。

这里简单介绍下Mono回收原理:

Mono会跟踪每次内存分配的动作,并维护一个分配对象表,当GC的时候,以全局数据区和当前寄存器中的对象为根节点,按照引用关系进行遍历,对于遍历到的每一个对象,将其标记为活的(alive)。所有对象的被标记意味着该对象可以通过全局对象或者当前上下文访问到,而没有被标记的对象则意味着该对象无法通过任何途径访问到,即该对象“失联”了,GC最终会将所有“失联”的对象内存进行回收。

内存泄露定义

我们把对象已经不再需要使用却没有被GC回收的情况称为mono内存泄漏。Mono内存泄漏会使空闲内存减少,GC频繁,mono堆不断扩充,最终导致游戏内存占用的升高。最终导致内存过高,进程被操作系统Kill或者崩溃。简单来说,也就是一些对象被实例化出来后没有被释放掉,一种保存在内存中,新的对象又需要申请新的内存空间,导致内存不断上升。

重点关注点

配置文件的使用、纹理、网格、RenderTexture和粒子系统;

比如频繁的创建销毁对象是否使用对象池,或者粒子,纹理等资源显示过后是否被及时从内存中释放,等等;

 

三.测试手段

1.首先通跑测试,确定问题确定原因,比如我上面通过通跑游戏确定存在内存泄露;

2.缩小范围,由于一个游戏在运行的过程中场景比较复杂,上面的同跑并不能准确定位问题,所以我们要划分场景测试,例如我在上面的通跑游戏过程中包括以下场景,打开关闭UI界面,战斗场景,切换地图,升级武器等,如果没有比较明显的数据,那就要分别针对以下场景进行测试。比如UI场景可以反复打开关闭UI界面,战斗场景可以持续战斗挂机,反复切换地图等等,总之是把游戏内进行的行为减少,细化要检测的场景;

3.定位问题

如果某个场景发生内存泄露,边定位到那个场景运行游戏,而在游戏运行时,相应的引擎也有一些工具可以查看具体的代码使用情况,比如unity的Profiler。

如果多个场景都出现内存泄露,那就要查找这些场景所交叉的部分,比如通信框架等;而本次经过多个场景的测试发现都存在泄露,最后经过排查发现是使用的通信框架存在泄露问题。

 

四,Perfdog内存相关简介

通常情况下安卓可以轻松获取到的内存有4种数据,我们也可以通过ADB来获取,

VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)

RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)

PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS

 

而Perfog的Memory也就是 Android PSS Memory,也是我们通常来用作代表内存的数据,是实际使用的物理内存大小。

Swap Memory (Swap Memory,部分设备支持Swap功能,在启用Swap功能后,系统会对PSS内存进行压缩,Swap增加,PSS会相应减少,由于压缩会占用CPU资源,同时相应会导致FPS降低)

Virtual Memory(VSS) 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

 

五、Perfdog新功能初探

PerfDog 3.5版本刚刚推出,新增一个最新的数值,CPU Usage(Normalized):规范化CPU利用率

官方给出的解释为:

 

传统计算方法:当前时刻CPU频率下,CPU Usage = CPU执行时间/CPU总时间。

由于移动设备CPU频率时刻变化,用传统CPU利用率计算方法,假定在低频率时刻计算出CPU利用率=30%,和在CPU高频时刻计算出CPU利用率=30%。同样都是30%但性能消耗是完全不样的,明显高频消耗更高。传统CPU利用率已无法真实反映性能消耗。

所以我们需要一种规范化(可量化)的统计方式。将频率因素考虑进去。

CPU Usage(Normalized)= (CPU执行时间/CPU总时间) * (当前时刻所有CPU频率之和/所有CPU频率最大值之和)。

PerfDog两种统计方式都有。CPU Usage默认为规范化CPU利用率。建议使用规范化CPU利用率作为衡量性能指标。

 

具体的描述可以看这里: 规范化CPU利用率

尝鲜体验以下。测试使用过程和之前的一样。来看看新增的数据对比

title:

CPU Usage趋势图对比:

 

CPU Core Usage趋势图对比:

 

从趋势图来看的话,实际上两种算法并无太大差异,但是精确到具体帧的使用率,差异会比较明显,单纯从性能的角度来说,传统CPU利用率仅能从数值的角度体现手机的CPU使用程度,但是无法从性能使用程度的角度表达手机的CPU使用效率,就像前文所说,低频率时刻计算出CPU利用率=30%,和在CPU高频时刻计算出CPU利用率=30%。同样都是30%但性能消耗是完全不样的。规范化CPU利用率数值可以弥补这一缺点。目前的测试行业良莠不齐,规范指标较少,如果真的可以做到统一行业标准不失为一件好事。

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [perfdog 内存泄漏] 推荐:

Perfdog玩转内存泄漏

- - 操作系统 - ITeye博客
最近QC同学在跑游戏的过程中发现玩的时间久了游戏会发生闪退,经过搜集信息后排除了功能性bug的. 拿到真机,USB连接,杀掉多余后台进程,打开Perfdog,接下来一顿操作猛如虎,Perfdog具体操作不在赘述,有关perfdog怎么使用的教程可以参考. 此图一出,基本就可以断定内存泄露了,这是正常玩游戏,游戏运行了30分钟的内存趋势图;.

内存泄漏

- - CSDN博客系统运维推荐文章
程序申请了堆空间,但是“忘记”释放,导致该块区域在程序结束前无法被再次使用导致的. 泄漏时间长了,就会导致用户空间内存不足,严重的导致死机. 如果泄漏比较严重,很容易察觉;但是有些泄漏很缓慢,不容易察觉,但是软件会运行很长时间后,会慢慢导致严重问题,而且当发现症状的时候,基本上已经是比较晚的时候了,想要识别泄漏,还是可以实现的,本篇文章来聊聊内存操作的原理.

java内存泄漏

- - 编程语言 - ITeye博客
不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址. Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Heap)中分配的,所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的. GC为了能够正确释放对象,会监控每个对象的运行状况,对他们的申请、引用、被引用、赋值等状况进行监控,Java会使用有向图的方法进行管理内存,实时监控对象是否可以达到,如果不可到达,则就将其回收,这样也可以消除引用循环的问题.

浅谈Java--内存泄漏

- - ITeye博客
      JAVA的垃圾回收机制,让许多程序员觉得内存管理不是很重要,但是内存内存泄露的事情恰恰这样的疏忽而发生,特别是对于Android开发,内存管理更为重要,养成良好的习惯,有利于避免内存的泄漏..     这里可以把许多对象和引用看成是有向图,顶点可以是对象也可以是引用,引用关系就是有向边.

Android 解析内存泄漏

- - CSDN博客移动开发推荐文章
1、引用没释放造成的内存泄露.        1.1、注册没取消造成的内存泄露.        这种 Android的内存泄露比纯 Java的内存泄露还要严重,因为其他一些Android程序可能引用我们的Anroid程序的对象(比如注册机制). 即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收.

shared_ptr真能防止内存泄漏吗?

- Roger - codedump
这个命题有些诡异,因为shared_ptr设计的初衷就是为了防止内存泄漏,但是先别急,等我把问题描述清楚.. 事出缘由是这几天项目出现一个内存泄漏的bug,之前这部分是使用shared_ptr封装了很多指针的操作,后来出于效率的考虑,改回了裸指针.由于我们使用的google tcmalloc做内存分配,它自带了检测内存泄漏的功能,于是在单元测试的时候就被检查出了内存泄漏..

(转)ThreadLocal的内存泄漏问题

- - 编程语言 - ITeye博客
原文:http://www.godiscoder.com/?p=479. 在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题. 表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出. 开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放.

Android内存泄漏检测-LeakCanary

- - CSDN博客推荐文章
添加LeakCanary依赖包. 在主模块app下的build.gradle下添加如下依赖:. 添加Application子类. 首先创建一个ExampleApplication,该类继承于Application,在该类的onCreate方法中添加如下代码开启LeakCanary监控:. 在配置文件中注册ExampleApplication.

Netty之有效规避内存泄漏

- - zzm
有过痛苦的经历,特别能写出深刻的文章 —— 凯尔文. 直接内存是IO框架的绝配,但直接内存的分配销毁不易,所以使用内存池能大幅提高性能. 但,要重新培养被Java的自动垃圾回收惯坏了的惰性. Netty有一篇必读的文档 官方文档翻译: 引用计数对象 ,在此基础上补充一些自己的理解和细节. 1.为什么要有引用计数器 .

Android性能优化之内存泄漏

- - CSDN博客推荐文章
  内存泄漏(memory leak)是指由于疏忽或错误造成程序未能释放已经不再使用的内存. 那么在Android中,当一个对象持有Activity的引用,如果该对象不能被系统回收,那么当这个Activity不再使用时,这个Activity也不会被系统回收,那这么以来便出现了内存泄漏的情况. 在应用中内出现一次两次的内存泄漏获取不会出现什么影响,但是在应用长时间使用以后,若是存在大量的Activity无法被GC回收的话,最终会导致OOM的出现.