Unity优化之GC——(一)认识堆(heap)&栈(stack)

本文详细解析了.NET框架下的内存管理机制,对比了堆和栈的特性与应用场景,阐述了值类型与引用类型在内存中的存储方式,以及垃圾回收的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

尽管在.NET framework 下我们并不需要担心内存管理和垃圾回收(GarbageCollection),但是我们还是应该了解它们,以优化我们的应用程序。 
同时还需要具备一些基础的内存管理工作机制的知识,这样有助于解释日常程序编写中的变量的行为。 
本文将学习和理解堆和栈的基本知识,变量类型以及为什么一些变量能够按照它们自己的方式工作。

在.NET framework环境下,当我们执行代码时,内存中有两个地方用来存储这些代码:堆和栈。

·Question1:堆和栈有什么不同?

    【申请速度】
    ·堆的申请速度较慢,容易产生内存碎片,但是用起来比较方便。
    ·而栈的申请速度较快,但却不受程序员的控制。
    【申请大小】
    ·栈申请的容量较小1-2M,堆申请大小虽受限于系统中有效的虚拟内存,但较大
    【存储内容】
    ·栈负责保存我们代码执行或调用的路径,而堆则负责保存对象或者说数据的路径。

可以将栈想象成一堆从顶向下堆叠的盒子。每当调用一次方法是,我们应将应用程序中所要发生的事情记录在栈顶的一个盒子中。而我们每次只能够使用栈顶的那个盒子。当我们栈顶的盒子被使用完之后,或者方法执行完毕之后,我们将抛开这个盒子然后继续使用栈顶上的新盒子。 
堆的工作原理比较相似,但大多数时候堆被用作保存信息而非保存执行路径,因此能够在任意时间被访问。 
与栈相比堆没有任何访问限制,堆就像一个仓库,存储着我们使用的各种对象等信息,而栈就像储物柜里堆叠的鞋盒,我们只能从最顶层的盒子开始取, 直到发现那只最适合的。 
和栈不同的是,堆中存储的信息在被调用完毕不会立即被清理掉。  

这里写图片描述

 

栈是自行维护的,也就是说内存自动维护栈,当栈顶的内容不再被使用,该内容将会被跑出。相反,堆需要考虑垃圾回收。

·Question2:堆和栈里有些什么?

当我们的代码执行的时候,堆和栈中主要放置了四种类型的数据:

    值类型(Value Type)
    引用类型(Reference Type)
    指针(Pointer)
    指令(Instruction)

1.值类型 
在C#中,所有被声明为一下类型的事物被称为值类型:

bool
byte
char
decimal
double
enum
float
int
long
sbyte
struct
unit
ulong
ushort


2.引用类型:

所有被声明为一下类型的事物被称为引用类型

class
interface
delegate
object
string
StringBuilder


3.指针

在内存管理方案中放置的第三种类型就是类型引用,引用通常就是一个指针。指针或引用是不同于引用类型的,这是因为当我们说某个事物是一个引用类型时就意味着我们是通过指针来访问它的。指针是一块内存空间,而它指向另一个内存空间。

这里写图片描述

大白话:

    对于引用类型而言,其内存地址(指针)存放在栈上
    其实际内容则由栈上的地址索引存储在堆上
 

4.指令

如何决定放哪? 
这里有一条黄金规则: 
1.引用类型总是放在堆中。 
2.值类型和指针总是放在他们被声明的地方。


就像前面提到的那样,栈是负责保存我们的代码执行或调用时的路径。当我们的代码开始调用一个方法时,将放置一段编码指令(在方法中)到栈上,紧接着放置方法的参数,然后代码执行到方法中被“压栈”至栈顶的变量位置。通过以下的例子很容易理解:

下面是一个方法:

public int PlusSix(int paramValue)
{
    int result;
    result = paramValue + 6;
    return result;
}


现在就来看看在栈顶发生了些神马。

首先方法入栈(只包含需要执行的逻辑字节,即执行该方法的指令,而非方法体内的数据),紧接着是方法的参数入栈。

 

接着,控制(即执行方法的线程)被传递到堆栈中PlusSix()的指令上,

 

当方法执行时,我们需要在栈上为“result”变量分配一些内存,

方法执行完成,然后方法的结果被返回。

通过将指针指向PlusSix()方法曾使用的可用的内存地址,所有栈上的该该方法所使用的内存都被清空,且程序将自动回到栈上最初的方法调用的位置(本例中不会看到)。


在这个例子中,我们的“result”变量是被放置在栈上的,事实上,当值类型数据在方法体重被声明时,它们都是被放置在栈上的。 
值类型数据有时也被放置在堆上。 
记住这条规则——值类型总是放在它们被声明的地方。好滴,如果一个值类型数据在方法体外被声明,而且存在于一个引用类型中,那么他将被堆中的引用类型所取代。

来看另外一个例子:

假如我们有这样一个MyInt类:

public class MyInt
{
    public int MyValue;
}



然后执行下面的方法:

public MyInt PlusSix(int praramValue)
{
    MyInt result = new MyInt();
    result.MyValue = praramValue + 6;
    return result;
}


就像前面提到的,方法以及方法的参数被放置到栈上,接下来,控制被传递到堆栈中的PlusSix()的指令上。

接着会出现一些有趣的现象……

因为“MyInt”是一个引用类型,它将被放置在堆上,同时在栈上生成一个指向这个堆的指针引用。

 

在PlusSix()方法执行之后,我们将清空刚刚使用的栈顶部分。

 

我们将剩下孤独的MyInt对象在堆中(栈中将不会存在任何指向 MyInt对象 的指针!)

这就是垃圾回收器(后简称GC)起作用的地方。 
当我们的程序达到了一个特定的内存阈值,我们需要更多的堆空间的时候,GC开始起作用。 
GC将停止所有正在运行的线程,找出堆中存在的所有不再被主程序访问的对象,并删除它们。 
然后GC会重新组织堆中所有剩下的对象来节省空间,并调整栈和堆中所有与这些对象相关的指针。 
你肯定会想到这个过程非常耗费性能,所以这是你就会知道为什么我们需要如此重视栈和堆里有些什么,特别是在需要编写提高性能的代码是。

太棒了,但它是如何影响我的?

GoodQuestion!

当我们使用引用类型时,我们实际上是在处理该类型的指针,而非该类型本身。当我们使用值类型是,我们是在使用值类型本身。
--------------------- 
作者:qq_26276097 
来源:优快云 
原文:https://blog.youkuaiyun.com/qq_26276097/article/details/75578886 
版权声明:本文为博主原创文章,转载请附上博文链接!

### Unity的概念 在计算机科学中,“”和“”是两种重要的内存管理机制,在 Unity 的开发过程中也广泛涉及这两种概念。 #### (Stack) 种基于 LIFO(Last In, First Out,后进先出)原则的数据结构。它用于存储局部变量以及函数调用的相关信息。的特点在于其操作速度快,因为它的地址空间连续且访问方式简单。然而,的空间有限,通常由操作系统设定大小限制[^1]。 在 Unity 开发中,当定义个值类型的变量(如 `int`、`float` 或者 `struct` 类型的对象)时,默认情况下这些对象会被分配到上。例如: ```csharp void ExampleFunction() { int number = 42; // 这是个值类型,被分配到上。 } ``` 旦该方法执行完毕,上的数据会自动释放,无需手动干预。 --- #### (Heap) 则是种灵活的内存区域,允许程序动态申请和释放内存。相比而言,的操作速度较慢,但由于其容量较大,适合存储生命周期较长或者不确定大小的数据。在 C# 和 Unity 中,引用类型(如类实例、字符串等)默认会在上分配内存[^2]。 以下是创建对象的例子: ```csharp class MyClass { } MyClass obj = new MyClass(); // 对象 "obj" 被分配到了上。 ``` 需要注意的是,中的垃圾回收是由 .NET 的 GC(Garbage Collector)负责处理的,开发者不需要显式删除对象,但这也可能导致性能开销增加。 --- ### 的主要区别 | **特性** | ** (Stack)** | ** (Heap)** | |------------------|---------------------------------------|----------------------------------------| | **存储位置** | 局部变量及函数调用 | 动态分配的大对象 | | **存取速度** | 高 | 较低 | | **内存管理** | 自动清理 | 手动或依赖 GC 清理 | | **适用场景** | 小规模临时数据 | 生命周期长、复杂结构 | --- ### 应用场景分析 - **的应用** 当需要快速分配并销毁少量数据时,优先考虑使用。比如循环计数器、简单的计算中间结果等。由于具有高效的存取特点,因此对于频繁使用的短期变量非常合适。 - **的应用** 如果涉及到复杂的对象关系网或者是大尺寸数组,则应该选择来保存它们。典型例子包括游戏实体组件树形结构、纹理贴图加载缓冲区等等。 --- ### 总结 理解 Unity的工作原理有助于编写更高效的游戏代码。合理利用两者可以减少不必要的内存消耗,并提升运行效率。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值