本文分享Unity中的资源管理-引用计数

在前面的文章中, 我们一起学习了对象池的基本原理和几种实现, 今天和大家继续聊聊另一个资源管理中比较重要的技术: 引用计数.

GC的基础知识

GC(Garbage Collection)是一种用来自动管理内存的方案, 开发者不需要过多的操心资源的释放问题, 很多语言(比如C#, Java)等, 都有自己的GC.

我们知道, 内存分配可以来自于栈和堆, 一般栈空间是跟随函数的生命周期, 在函数使用期间申请, 在函数使用后释放, 一般在函数内部申请的局部变量(非new关键字申请), 在函数执行完毕后就会被销毁. 所谓函数栈就是指函数栈空间, 每个函数各自独立.

而来自堆空间的申请的内存(一般是new关键字申请)的生命周期会超过函数, 甚至超过类, 是整个程序共享的. 这种内存理论上是无限的大(受程序内存影响), 要求开发者自己申请, 自己释放(因为跨越了作用域).

在没有GC的语言, 比如C/C++中, 开发者需要自己在合适的地方释放内存, 不然就会造成内存泄漏(某块内存不再使用, 但是无法释放), 使用这些语言是需要十分小心的, 所以这些语言的门槛一般比拥有GC的语言高. 相信各位都曾经被指针, 引用等概念所折磨过吧.

在GC的语言中, 将堆内存进一步划分为托管堆非托管堆. 顾名思义, 托管堆就是由GC托管, 维护, 而非托管堆由开发者自己管理(比如文件引用).

拥有GC后, 内存释放的大旗由GC扛过去了, 大部分情况下, 开发者不再操心内存释放的问题, 这极大的降低了开发门槛, 让开发者更加专注于业务, 不必时时刻刻小心内存的问题.

当然, 有好处那就有坏处, 不然C/C++这些语言早就淘汰了.

有GC的语言, 最大的缺点还是在于GC本身.

首先, GC本身需要占用一定的内存, 消耗一定的性能, 无法像C的语言一样, 充分发挥性能. 即拥有GC的语言天然就比不拥有GC的语言更大更复杂更慢(这里只是说普遍情况).

其次, GC的回收总不会十分及时, 虽然能够进行手动干预, 但是也会缺失很大的灵活性和及时性.

最后, 习惯于GC的开发者总是对GC有很大的信任, 习惯于放飞自我, 从而很有可能无法正确的使用内存, 造成内存泄漏.

什么是引用计数

虽然大部分内存可以被托管, 我们无需操心, 但是对于非托管的内存, 比如游戏中用到的各种资源, texture, sound, prefab等, 我们还是需要自己来维护的.

最常用的方案就是引用计数.

简单的说, 引用计数就是为对象的每一个引用都维持一个计数, 释放引用只是将计数减一, 只有在计数为0时才执行真正的对象销毁.

在游戏客户端开发中, 占用大量内存的其实就是各种各样的资源, 而资源是有限的, 并且申请, 初始化, 销毁资源的代价一般比较昂贵, 所以引用计数更多的会用来管理这些内存, 在真正需要的时候才申请, 使用和销毁.

引用计数的原理

和对象池一样, 引用计数的原理非常简单, 但是要用好却并不容易, 简单说就是谁申请谁释放, 申请和释放"成对"使用.

下面我们定义一个引用计数接口和类, 并附加一个简单的测试用例:

public interface IRefCounter {// 引用计数int refCount {get;}// 增持void Retain();// 减持void Release();// 为零时的操作void OnRefZero();
}public class RefCounter : IRefCounter {public int refCount {get; private set;}// 加1public void Retain() {refCount++;}// 减1public void Release() {refCount--;if (refCount == 0) {OnRefZero();}}public virtual void OnRefZero() {Debug.Log("释放资源")}
}// 进出房间, 第一个进房间开门, 最后一个离开房间关门
public class Door : RefCounter {public void SwitchOn() {Retain();if (refCount > 1) return;Debug.Log("第一个进房间, 开门.");}public void SwitchOff() {Release();}public override void OnRefZero() {Debug.Log("最后一个离开房间, 关门.");}
}public class Person {private Door m_Door;// 进房间相当于申请内存并持有(count++)public void EnterDoor(Door door) {m_Door = door;door.SwitchOn();}// 出房间相当于释放内存并减持(count--)public void LeaveDoor() {Assert.IsNotNull(m_Door, "需要先进房间!");m_Door.SwitchOff();m_Door = null;}
}public class RefCounterTest : MonoBehaviour {public Button btnOpen;public Button btnClose;private List<Person> m_AllPerson = new List<Person>();private void Awake() {var door = new Door();// 每进一个人, 开一次门, 只有第一个才是真正的开门btnOpen.onClick.AddListener(() => {var person = new Person();person.EnterDoor(door);m_AllPerson.Add(person);});// 每退出一个人, 关一次门, 只有最后一个才真正关门btnClose.onClick.AddListener(() => {var person = m_AllPerson.FirstOrDefault();if (person == null)return;person.LeaveDoor();m_AllPerson.Remove(person);});}private void OnDestroy() {foreach(var person in m_AllPerson) {person.LeaveDoor();}m_AllPerson.Clear();}
}

代码比较简单, 就是每个人进门时做一个计数, 所有人出门后才关门.

自动回收

想象一下, 假设我们的门建设在游戏<<我的世界>>中, 在所有人离开后不仅要关门, 还要将门整个销毁并回收材料, 只需要在上面的实现中, 关门的时候加上资源回收即可.

但是这里依然有一个问题, 就是如果门建立好以后, 一直没有人进去怎么办? 我们什么时候回收呢?

这里就可以使用所谓自动释放池(Autorelease Pool), 在每个门创建之后, 为了最后都能回收, 先将其交给一个管理器, 在帧的末尾进行释放, 如果在此之前有人进门, 则不会进行回收, 而是延迟到所有的人出门, 如果在帧的末尾之前还没有人进门, 就直接释放. 说起来比较麻烦, 实现起来超级简单:

public class AutoReleasePool : Singleton<AutoReleasePool> {private List<IRefCounter> m_AutoReleaseCounter = new List<IRefCounter>();public void AddCounter(IRefCounter counter) {m_AutoReleaseCounter.Add(counter);}public void AutoRelease() {foreach(var counter in m_AutoReleaseCounter) {counter.Release();}m_AutoReleaseCounter.Clear();}
}public interface IRefCounter {//.....// 自动释放void AutoRelease();
}public class RefCounter : IRefCounter {//.....// 自动释放public void AutoRelease() {AutoReleasePool.instance.AddCounter(this);}
}public class Door : RefCounter {//.....public override void OnRefZero() {Debug.Log("关门并销毁, 回收资源.");}
}public class RefCounterTest : MonoBehaviour {//.....private void Awake() {var door = new Door();door.AutoRelease();//.....}//.....private void LateUpdate() {AutoReleasePool.instance.AutoRelease();}
}

每一帧的末尾都进行减持, 如果没有其它持有者, 就会触发回收. 在帧末尾之前使用完毕, 也只会在自动释放池中进行回收.

自动释放池相当于一个保险, 避免申请了但是实际却没有使用的问题.

总结

引用计数的原理和实现很简单, 但是有各种各样的使用方法, 在不同的情况有不同的变体, 但是总是绕不过一个核心就是: 谁申请谁释放, 申请和释放成对使用. 只要牢牢抓住这个核心, 再复杂的实现也能轻易理解.

今天给大家介绍了引用计数的基本概念, 并提供了一个简单的例子, 最后还添加了一个自动释放池作为保险, 这足以应对大部分应用场景了.

目前的内容都是资源管理的前置条件, 在接下来的几篇文章中, 我们将正式进入Unity中的资源管理内容, 结合Unity官方的方法和这些前置理论就可以构成一套完整的大型商业项目可以使用的资源管理方案. 希望感兴趣的同学持续关注.

好了, 今天的内容就是这些, 希望对大家有所帮助.

Unity中的资源管理-引用计数相关推荐

  1. Unity中的资源管理-对象池技术(3)

    本文分享Unity中的资源管理-对象池技术(3) 在上两篇文章中, 我们一起学习了普通类的两种对象池实现, 今天接着介绍Unity中GameObject(主要是预制实例化对象)的对象池. GameOb ...

  2. Unity中的资源管理-AssetBundle(1)

    本文分享Unity中的资源管理-AssetBundle(1) 在上一篇文章中, 我们简单介绍了Unity中的资源和基本的使用, 今天我们详细介绍下使用AssetBundle来管理资源. AssetBu ...

  3. Unity中的资源管理-几种常见的序列化方式

    本文分享Unity中的资源管理-几种常见的序列化方式 在网游客户端的开发中, 大部分数据只需要从服务器获取数据之后存放在内存中, 但是仍然有一些数据需要做序列化, 并持久化存放在客户端本地. 比如用户 ...

  4. 关于UNITY中System.Drawing引用失败的处理方法

    关于UNITY中System.Drawing引用失败的处理方法 今天在使用EPPlus中,遇到一个处于This type has been forwarded to assembly 'System. ...

  5. 关于Unity中的资源管理,你可能遇到这些问题(UWA报告)

    关键字 AssetBundle 资源制作 纹理\网格\材质\Shader\音频\动画 Lightmap 一.AssetBundle 相关 Q1:Unity中的SerializedFile是怎么产生的? ...

  6. 关于Unity中的资源管理,你可能遇到这些问题

    关键字 AssetBundle 资源制作 纹理\网格\材质\Shader\音频\动画 Lightmap 一.AssetBundle 相关 Q1:Unity中的SerializedFile是怎么产生的? ...

  7. Unity中项目资源管理的一些经验与总结

    欢迎大家去我的小站Reset研究所坐坐:原文地址 原本想写一下Unity中通过资源来减小游戏中内存性能的文章,不过我一直采用的还是打图集.压资源.延迟加载等老一套,感觉不是很够讲所以就只讲了一部分优化 ...

  8. OC中的自动引用计数

    目录: 1,自动引用计数的定义 2,强引用和弱引用 3,类比手动引用 4,循环引用 5,CoreFoundation 内容: 自动引用计数的定义: (Automatic Reference Count ...

  9. Python之美[从菜鸟到高手]--一步一步动手给Python写扩展(异常处理和引用计数)

    我们将继续一步一步动手给Python写扩展,通过上一篇我们学习了如何写扩展,本篇将介绍一些高级话题,如异常,引用计数问题等.强烈建议先看上一篇,Python之美[从菜鸟到高手]--一步一步动手给Pyt ...

最新文章

  1. 怎么将一个十进制数转化为二进制数并打印出来
  2. elasticsearch快照和恢复
  3. 第四章 Tomcat服务器的安装及配置2
  4. STL源码剖析—stl_config
  5. linux双屏播放视频,Ubuntu Linux下双屏显示解决方案
  6. [Android] Bitmap OOM解决办法一
  7. 根据流程部署ID来获取流程定义图片
  8. Windows server 2008设置远程桌面
  9. IBM与红帽联手构建开源混合云环境
  10. Java微信公众平台开发(十五)--微信JSSDK的使用
  11. 数字调制解调—扩频通信和伪码同步
  12. spreadjs使用
  13. 使用FIT2CLOUD在青云QingCloud快速部署和管理Kubernetes集群 1
  14. 曼切斯特编码波特率和比特率的关系
  15. Win10 安装 Ubuntu 使用 Linux 教程
  16. 联合分布及其随机变量
  17. 计算机添加usb网络打印机,方便实用!教您如何简单地将USB打印机更改为无线打印机!...
  18. leaflet地图原理_Web地图呈现原理
  19. 将货币转换为大写形式用c语言,C#:小写金额转换为大写
  20. 树莓派笔记8:UDP传输视频帧

热门文章

  1. 计算机动画与游戏考研,上海交通大学电子信息与电气工程学院专业学位课程内容介绍《计算机动画建模与渲染》...
  2. IP地址翻译成实际的物理地址
  3. Android开发者选项中,这29个功能你忽略了吗?
  4. vb.net 网络文件下载完成代码_手机资讯:iPhone信号不好怎么办或是运营商配置文件惹的祸...
  5. 在下列选项中不属于python特点的是_在下列选项中,不属于 Python特点的是( )。_学小易找答案...
  6. ios 关于Guideline 2.1 - Information Needed
  7. dynamic time warping matlab,动态时间规整(Dynamic Time Warping)
  8. 【前端静态页面HTML】
  9. 医院管理系统(新手适用)
  10. FinalShell 远程连接ubuntu一直提示输入密码