面试问题:堆与栈(C#)
前言
堆与栈
前言
最近我失业了,为了找新工作,这几周我频繁参加技术面试。尽管每个项目都各有不同,但面试官往往会不时地问一些相同的问题。我决定记录下我经常遇到的面试问题,并写下我的答案。
当你处于换工作状态时,准备面试可能会很乏味。所以我欢迎你关注这个系列,我会尝试在每篇文章中涵盖不同的面试主题。我会定期更新这些文章,希望对大家有所帮助。
注意:如果您发现错误,请在评论中告诉我,我会纠正。谢谢!
堆与栈
问:在 C# 中对象分配在哪里?
在 C# 中,有两个地方可以存储对象——堆和栈。
堆栈上分配的对象仅在堆栈框架(方法的执行)内部可用,而堆上分配的对象可以从任何地方访问。
问:哪些对象分配在栈上,哪些对象分配在堆上?
注意:你永远不应该说“引用类型在堆上分配,而值类型在堆栈上分配”,这是一个常见的错误,会给经验丰富的面试官敲响警钟。
引用类型(类、接口、委托)始终在堆上分配。
当你将一个引用对象作为参数传递或赋值给一个变量时,你实际上是在传递它的引用。引用(而不是被引用的对象)可以在栈或堆上分配。
通过传递对对象的引用,您可以知道该对象在堆上的位置,以便您的代码可以访问它。
每次将对象作为引用传递时,引用本身都会被复制。这意味着您可以将引用更改为指向其他对象,而不会影响先前的对象本身或指向该对象的其他引用。引用是轻量级的,并且始终是恒定大小(32 位或 64 位,具体取决于操作系统位数),因此复制它(并因此传递引用类型)被认为是廉价的。
值类型(派生自System.ValueType
,例如int
,,bool
和char
任何enum
)struct
可以在堆或堆栈上分配,具体取决于它们在何处声明。
- 如果值类型在方法内部被声明为变量,那么它将存储在堆栈中。
- 如果值类型被声明为方法参数,那么它将存储在堆栈中。
- 如果值类型被声明为某个类的成员,那么它将与其父类一起存储在堆上。
- 如果值类型被声明为结构的成员,那么它将存储在该结构存储的任何位置。
从 C#7.2 开始,astruct
可以声明为ref struct
,在这种情况下它将始终在堆栈上分配,从而防止在引用类型内声明它。
值类型的实例通过复制传递(除非使用引用语义,见下文)。这意味着每次将值类型赋值给变量或作为参数传递时,该值都会被复制。
由于复制值类型的成本可能会根据对象的大小而变得昂贵,因此不建议将占用大量内存的对象声明为值类型。
由于 C# 中的每个类型都派生自System.Object
,因此值类型可以分配给变量或传递给需要 的方法object
。在这种情况下,值会被复制并存储在堆上,包装成引用类型,这一操作称为装箱。
问:我们可以使用具有引用语义的值类型吗?
诸如ref
and out
、ref return
and ref local
(C#7.0)、in
(C#7.2) 等关键字允许通过引用访问值类型。这意味着,使用代码不会复制值,而是会接收该值的引用(无论该引用位于堆栈还是堆上),只要该值类型的生命周期比使用代码的生命周期长即可。
问:如何释放堆内存?
虽然在弹出包含堆栈的框架时,堆栈上存储的对象会消失,但堆上存储的对象所使用的内存需要由垃圾收集器释放。
当存储在堆上的对象不再具有指向它的任何引用时,它就被认为有资格进行垃圾收集。
在某个时刻,垃圾收集器启动,中断所有正在运行的线程,调用它试图摆脱的对象的终结器(在特殊的终结器线程上),然后将内存标记为可自由使用。
问:在堆上分配和释放内存可能会出现什么问题?
随着堆内存的分配和释放,堆中会产生碎片。如下图所示:
HEAP:
---][-------][----------][-----]........
obj 1 obj 2 obj 3 free
当obj 2
被取消分配时,其内存将被释放:
HEAP:
---][-------]............[-----]........
obj 1 free obj 3 free
现在,如果运行时需要在堆上分配另一个对象,它可以使用 释放的内存obj 2
,但前提是新对象确实“适合”。如果该内存不足,运行时可能会通过扩展其工作集向操作系统请求更多连续内存,如下所示:
HEAP:
---][-------]............[-----][--------------------]...
obj 1 free obj 3 obj 4
由于碎片化,内存使用效率会降低。为了解决这个问题,垃圾收集器可能会重新整理内存,使之不再有空隙。这可以通过简单地复制字节来实现,这一操作称为“碎片整理”。
HEAP:
---][-------][-----][--------------------]...............
obj 1 obj 3 obj 4 free
问:什么是大对象堆以及它有什么用途?
根据消耗的内存大小,内存碎片整理的成本可能很高,这就是为什么堆被进一步分为小对象堆(SOH)和大对象堆(LOH)。
如果对象小于 85k 字节,则存储在 SOH 中,否则存储在 LOH 中。85000 字节的临界点是根据经验设计的,超过该临界点后,碎片整理将不再提供性能优势。
由于 CPU 处理double
s 的方式,数组double
是一个例外,如果数组中的元素超过 1000 个,则此类对象将存储在 LOH 上。
LOH 中的内存(通常)不会进行碎片整理,从而以较低内存使用效率为代价提供更好的性能。
鏂囩珷鏉ユ簮锛�https://dev.to/tyrrrz/interview-question-heap-vs-stack-c-5aae