Unity面经-值类型与引用类型
本文比较了值类型和引用类型的区别,重点介绍了String的特殊性质及其不变性特征。分析了装箱拆箱的原理及其性能影响,建议使用泛型避免频繁装箱。详细讲解了.NET垃圾回收机制,包括标记-压缩算法和分代回收策略,并对比了Unity采用的贝姆垃圾回收机制的局限性。全文围绕内存管理和性能优化展开,为开发者提供了实用的编程建议。
一、直观比较
| 值类型 | 引用类型 | |
| 继承父类 | System.ValueType,System.ValueType继承System.Object | System.Object |
| 内存存放位置 | 栈 | 堆 |
| 大小 | 每个类型固定大小 | 不固定大小 |
| 回收时机 | 操作系统控制,作用域结束 | GC控制 |
| 是否密封 | 是 | 不是 |
| 比较Equals | 重写了Equals,比较的是内容数值 | 比较的是内存地址是否相同 |
二、(特殊)String
String是引用类型,但是实际使用起来还是值类型。
String的不变性
string 对象称为不可变的(只读),因为一旦创建了该对象,就不能修改该对象的值。有的时候看来似乎修改了,实际是string经过了特殊处理,每次改变值时都会建立一个新的string对象,变量会指向这个新的对象,而原来的还是指向原来的对象,所以不会改变。这也是string效率低下的原因。如果经常改变string的值则应该使用StringBuilder而不使用string。
String是引用类型,只是编译器对其做了特殊处理。
三、装箱与拆箱
3.1装箱
值类型转化为引用类型。
int number = 42; // 值类型
object boxedNumber = number; // 装箱
或将值类型作为参数传递给了参数为object的方法:
void PrintObject(object obj)
{
Console.WriteLine(obj);
}
PrintObject(1); // 装箱发生
内存过程是:
1. 在堆上为装箱对象分配内存。
2. 将值类型的值分配到开辟的内存上。
3. 将开辟内存的引用放在栈中,通过引用访问这个对象。
3.2 拆箱
引用类型转化为值类型
object boxedNumber = 42; // 装箱
int number = (int)boxedNumber; // 拆箱
3.3 装箱与拆箱的缺点
1. 堆的内存分配比栈的慢,可能是因为堆的内存分配是相对随机的。
2. 在堆上分配内存会增加gc负担。
3. 拆箱时候要进行类型检查。
3.4 如何避免
使用泛型和合适的数据结构。比如List<T>而不是ArrayList。List支持泛型,ArrayList不支持。
ArrayList list = new ArrayList();
list.Add(number); // 装箱发生
四、垃圾回收GC
4.1 标记-压缩
通过一个图的数据结构来收集对象的根,这个根就是引用地址。可以理解为指向托管堆的关系线。当触发这个算法时,会检查图中的每个根是否可达,如果可达,则对其标记,然后在堆上找到剩余没有标记的对象进行删除。
4.2 分代
在主流 .NET/Mono/IL2CPP 里,托管 GC 的核心思想基本是:
-
分代(Generational GC)
-
堆被分成几代:Gen0、Gen1、Gen2(有的实现还有 LOH – Large Object Heap)。
-
假设:“大部分对象都很快就死了,很少有对象活得特别久”(弱代假说)。
-
所以会频繁地只扫描 Gen0,不总是全堆扫描。
-
-
标记 – 清除 / 标记 – 压缩(Mark & Sweep / Mark & Compact)
-
从“根对象”(栈上的局部变量、静态变量等)出发,遍历可达对象并标记为 alive。
-
其他没被标记到的就是“不可达(垃圾)”。
-
清理时:
-
要么直接把这些垃圾块记成“空闲区”(Mark & Sweep);
-
要么把活着的对象向一侧压缩,腾出连续空间(Mark & Compact),好处是减少碎片,但需要移动对象,更新引用。
-
-
大部分现代的 .NET GC 都是一个组合拳,比如:
-
小对象:分代 + 标记压缩
-
大对象:单独放一个堆(LOH),只做标记清除、减少移动
4.3 Unity的GC
5.x版本前不会分代和多线程。
当你用 IL2CPP(例如 iOS、安卓、微信小游戏经常用)时:
-
C# 编译成 C++,再用平台编译器编译,但托管堆仍然由 Unity 自己的 GC 管理。
-
Unity 在 IL2CPP 下使用的是 自家的 Boehm 派生 / Unity GC,现在也支持 增量 GC。
-
算法思想同样是:
-
标记-清除 / 标记-压缩 + 增量执行,
-
仍然是典型的 追踪式 GC(Tracing GC),不是引用计数。
-
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐


所有评论(0)