Unity UI内存泄漏优化
项目一运行,占用的内存越来越多,不会释放,导致GC越来越频繁,越来越慢,这些都是为什么呢,今天从UI方面谈起。
首先让我们来聊聊什么是内存泄漏呢?
一般来讲内存泄漏就是指我们的应用向内存申请了一块地址,然后这块地址的相关引用全部丢失了,这块内存无法再被分配,在计算机眼里,那就是丢了,找不回来了,除非重启。。。
不过,这里如果我们要去理解Unity中的内存泄漏,那我们首先要了解一下Unity的内存分配机制和GC机制,哇,不过说真的,要真是细说这两点,那真是几天都讲不完呀,还是算了,哈哈,这里大概聊一下,
程序在运行的时候,会先从计算机中申请一块内存,这时候如果我们需要去申请一块地址的时候,Unity会先去从堆内存中找合适大小的地址块给我们,但是这时候,如果堆内存用完了,这时候GC就出马了,会先清理一遍当前内存中无用的数据然后给我们分配所需要的内存块,那这个时候如果GC之后还是没有找到足够大小的内存给我们用怎么办呢,Unity只能去在申请一块之前内存2被大小的内存了。
这时候来想想,如果在我们的项目中这如果不断重复上述步骤,那么这时候是不是就意味着内存泄漏了呢。。。 现在就让我们开始从实际情况来一探究竟吧!!!
一开始我们通过Unity的Profiler工具只能看到在我们的UI已经关闭销毁了可是UI里面用到的图集还在内存里面存在,不应该呀,如果图集不释放,那岂不是意味着我们如果打开很多UI的时候,这些图集资源就要占到很多内存,如何查看当前内存中图集情况,可以参考下图,先选中Memory模块,然后选择Detailed,点击Take Sample Playmode,这时候内存中的图集就出现在下面了,参考5的位置,这里说明一下位置4这个选项,如果不勾选,进行内存采样速度会快很多,勾选了会慢很多,但是会同时采样出对应资源当前的引用情况。

这时候我们通过对游戏中不同节点进行内存采样,便能分析出我们哪些图集没有随着预设的销毁而销毁。
问题已经找到了,那么如何解决呢,如何下手呢,这时候又不知道怎么办了,害!!!

但是生活还要继续,问题还得解决呀,那么接下来就开始了问题分析,无数次Demo测试,从AB包加载卸载,到Unity内存分配管理,从GC的工作方式,到GC的底层实现原理,终于发现了这几个问题。
首先,如果我们的项目是通过AssetBundle方式加载的,那么在我们切场景或者进行阶段变化的时候我们需要处理一下无用资源的释放,调用一下下面的接口。
Resources.UnloadUnusedAssets();
卸载未使用的资源
这时候我们在进行内存对比分析的时候会发现会有一些内存被释放,可是图集不销毁的问题还在,害,还以为挺简单的,目前看来问题更复杂了。。。
这时候用上了另一个工具Memory Profiler,这个工具是在Unity2020之后的版本推出的功能,对当前内存进行快照,可视化的形式显示当前内存分配的大小,列出了每个托管对象的类型,值,占用大小,地址,被引用链等等信息,还可以进行快照对比,分析两次内存快照新增、删除和保持不变的内存对象,从而更方便快捷的定位项目内存的使用情况。
通过对内存进行快照,分析图集的引用链,屏蔽代码,重新快照测试,一次次的测试,慢慢缩小代码范围,定位图集不销毁的原因,最终发现原来是我们的UI使用了static实例来实现单例效果,在其他地方调用,但是在我们UI不需要的时候并没有将这个静态单例设置为null,导致整个UI资源的相关引用一直存在,无法释放,还有就是我们在对按钮进行事件注册的时候,使用了项目封装的接口,而项目封装的接口在拿到委托事件对象后,并没有在移除事件的时候去清除委托事件对象,导致引用一直存在,相关的资源也就无法释放。

相信经过上述步骤之后我们的图集不销毁问题已经解决了大部分了,具体还有哪些,后面有需要我们再补充,哈哈。
这里再说一个图片不销毁问题,在项目中我们经常会去动态替换某些图片来实现我们的功能,这时候有一个统一接口就很方便了,可是图片不销毁问题也正好跟这个动态替换接口有关,由于我们的统一接口会保存一份加载的图片的引用,在对应预设销毁的时候,由于图片引用一直存在,所以图片就无法被GC处理掉,这时候我们可以考虑对我们动态加载的图片进行场景管理,在合适的时候清空一次引用列表,还有由于我们动态图片加载是自己管理加载资源,所以我们在清空列表的时候要调用一次对应接口的卸载资源接口,否则,资源还是无法从内存中释放。
目前为止,图集图片不销毁问题已经解决了大部分,至于项目中具体还有没有其他问题导致,有待后续研究,,,总结一下:
- 使用了static静态类方式来实现单例的UI,在使用完之后一定记得将对应单例设置为null,让GC可以去释放对应的内存。
- 在使用委托或者其他时候,拿到类对象的引用在使用完之后一定要记得释放引用。
- 加载的资源在不适用的时候记得卸载掉,比如AssetBundle.Load()和AssetBundle.Unload()
- 在适当的时机调用Resource.UnloadUnusedAssets()接口释放无用的资源
简而言之,言而简之,内存优化一直是项目开发中的重头戏,任重而道远呀。。。

心怀梦想 奔向远方
相关文章:
Unity UI内存泄漏优化
项目一运行,占用的内存越来越多,不会释放,导致GC越来越频繁,越来越慢,这些都是为什么呢,今天从UI方面谈起。 首先让我们来聊聊什么是内存泄漏呢? 一般来讲内存泄漏就是指我们的应用向内存申请…...
学习笔记:Opencv实现图像特征提取算法SIFT
2023.8.19 为了在暑假内实现深度学习的进阶学习,特意学习一下传统算法,分享学习心得,记录学习日常 SIFT的百科: SIFT Scale Invariant Feature Transform, 尺度不变特征转换 全网最详细SIFT算法原理实现_ssift算法_Tc.小浩的博客…...
【golang】接口类型(interface)使用和原理
接口类型的类型字面量与结构体类型的看起来有些相似,它们都用花括号包裹一些核心信息。只不过,结构体类型包裹的是它的字段声明,而接口类型包裹的是它的方法定义。 接口类型声明中的这些方法所代表的就是该接口的方法集合。一个接口的方法集…...
【Linux操作系统】Linux系统编程中的共享存储映射(mmap)
在Linux系统编程中,进程之间的通信是一项重要的任务。共享存储映射(mmap)是一种高效的进程通信方式,它允许多个进程共享同一个内存区域,从而实现数据的共享和通信。本文将介绍共享存储映射的概念、原理、使用方法和注意…...
2235.两整数相加:19种语言解法(力扣全解法)
【LetMeFly】2235.两整数相加:19种语言解法(力扣全解法) 力扣题目链接:https://leetcode.cn/problems/add-two-integers/ 给你两个整数 num1 和 num2,返回这两个整数的和。 示例 1: 输入:num…...
中国剩余定理及扩展
目录 中国剩余定理解释 中国剩余定理扩展——求解模数不互质情况下的线性方程组: 代码实现: 互质: 非互质: 中国剩余定理解释 在《孙子算经》中有这样一个问题:“今有物不知其数,三三数之剩二&#x…...
数据在内存中的存储(deeper)
数据在内存中的存储(deeper) 一.数据类型的详细介绍二.整形在内存中的存储三.浮点型在内存中的存储 一.数据类型的详细介绍 类型的意义: 使用这个类型开辟内存空间的大小(大小决定了使用范围)如何看待内存空间的视角…...
算法修炼Day52|● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组
LeetCode:300.最长递增子序列 300. 最长递增子序列 - 力扣(LeetCode) 1.思路 dp[i]的状态表示以nums[i]为结尾的最长递增子序列的个数。 dp[i]有很多个,选择其中最大的dp[i]Math.max(dp[j]1,dp[i]) 2.代码实现 1class Solution {2 pub…...
使用 HTML、CSS 和 JavaScript 创建实时 Web 编辑器
使用 HTML、CSS 和 JavaScript 创建实时 Web 编辑器 在本文中,我们将创建一个实时网页编辑器。这是一个 Web 应用程序,允许我们在网页上编写 HTML、CSS 和 JavaScript 代码并实时查看结果。这是学习 Web 开发和测试代码片段的绝佳工具。我们将使用ifram…...
百望云联合华为发布票财税链一体化数智解决方案 赋能企业数字化升级
随着数据跃升为数字经济关键生产要素,数据安全成为整个数字化建设的重中之重。为更好地帮助企业发展,中央及全国和地方政府相继出台了多部与数据相关的政策法规,鼓励各领域服务商提供具有自主创新的软件产品与服务,帮助企业在合规…...
实现两个栈模拟队列
实现两个栈模拟队列 思路:可以想象一下左手和右手,两个栈:stack1(数据所在的栈) ,stack2(临时存放)。 入队:需要将入队 num 加在 stack1 的栈顶即可; 出队&am…...
无涯教程-TensorFlow - 单词嵌入
Word embedding是从离散对象(如单词)映射到向量和实数的概念,可将离散的输入对象有效地转换为有用的向量。 Word embedding的输入如下所示: blue: (0.01359, 0.00075997, 0.24608, ..., -0.2524, 1.0048, 0.06259) blues: (0.01396, 0.11887, -0.48963, ..., 0.03…...
Facebook AI mBART:巴别塔的硅解
2018年,谷歌发布了BERT(来自transformers的双向编码器表示),这是一种预训练的语言模型,在一系列自然语言处理(NLP)任务中对SOTA结果进行评分,并彻底改变了研究领域。类似的基于变压器…...
BDA初级分析——SQL清洗和整理数据
一、数据处理 数据处理之类型转换 字符格式与数值格式存储的数据,同样是进行大小排序, 会有什么区别? 以rev为例,看看字符格式与数值格式存储时,排序会有什么区别? 用cast as转换为字符后进行排序 SEL…...
汽车后视镜反射率测定仪
后视镜是驾驶员坐在驾驶室座位上直接获取汽车后方、侧方和下方等外部信息的工具。它起着“第三只眼睛”的作用。后视镜按安装位置划分通常分为车外后视镜、监视镜和内后视镜。外后视镜观察汽车后侧方监视镜观察汽车前下方内后视镜观察汽车后方及车内情况。用途不一样镜面结构也…...
Redis学习笔记
redis相关内容 默认端口6379 默认16个数据库,初始默认使用0号库 使用select 切换数据库 统一密码管理,所有库密码相同 dbsize:查看当前库key的数量 flushdb:清空当前库 flushall:清空全部库 redis是单线程 多路…...
韩顺平Linux 四十四--
四十四、rwx权限 权限的基本介绍 输入指令 ls -l 显示的内容如下 -rwxrw-r-- 1 root 1213 Feb 2 09:39 abc0-9位说明 第0位确定文件类型(d , - , l , c , b) l 是链接,相当于 windows 的快捷方式- 代表是文件是普通文件d 是目录,相…...
【支付宝小程序】分包优化教程
🦖我是Sam9029,一个前端 Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-JS学习,CSS学习,Vue-2领域博主 🐱🐉🐱🐉恭喜你,若此文你认为写的不错,不要吝啬你的赞扬,求收…...
语言基础2 矩阵和数组
语言基础2 矩阵和数组 矩阵和数组是matlab中信息和数据的基本表示形式 可以创建常用的数组和网格 合并现有的数组 操作数组的形状和内容 以及使用索引访问数组元素 用到的函数列表如下 一 创建 串联和扩展矩阵 矩阵时按行和列排列的数据元素的二维数据元素的二维矩…...
springMVC中过滤器抛出异常,自定义异常捕获
在过滤器中引入org.springframework.web.servlet.HandlerExceptionResolver AutowiredQualifier("handlerExceptionResolver")private HandlerExceptionResolver resolver; // doFilter中处理if (条件1) {if (条件2) {resolver.resolveException(request, response, …...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
