Stack vs. Heap (.NET)

Stack

Известен также как стек потока (thread stack) или стек вызовов (call stack).

  • Небольшая область оперативной памяти (несколько Мб, точный размер зависит от платформы).

  • Древнейший механизм. Известен со времён Тьюринга.

  • Работает по принципу стопки (LIFO). Допустимые операции — добавить наверх (push) и снять сверху (pop). Не нужен поиск или какой-либо “уход” за структурой. Поэтому стек очень быстрый.

  • Через стек организуется вызов методов, передача аргументов, хранение локальных переменных и любых других данных с предопределённым временем жизни.

  • Авторитетные люди предлагают считать стек деталью реализации.

  • Но на практике важно понимать, что́ и в каких случаях остаётся на стеке, а что уходит в кучу. Это влияет на производительность.

  • Evaluation stack на уровне IL — это другое. “Настоящий” стек появляется вместе с процессом/потоком на уровне операционной системы.

  • У каждого потока свой изолированный стек.

  • CLR умеет ходить по стеку (stack walk, stack crawl), определять границы методов (кадры, фреймы), аргументы и переменные; отделять управляемый код от неуправляемого; формировать stack trace; делать unwind при ловле исключений.

Если на Windows подключиться к .NET-процессу отладчиком с галочкой Native, то можно увидеть путь от ядра через C++ до Program.Main:

Name Language
Project.dll!Program.Main(...) Line 29 C#
[Native to Managed Transition]
hostpolicy.dll!coreclr::execute_assembly(...) C++
. . .
dotnet.exe!__scrt_common_main_seh() Line 288 C++
kernel32.dll!BaseThreadInitThunk() Unknown
ntdll.dll!RtlUserThreadStart() Unknown

А вот стек потока, запущенного через Thread.Start:

Name Language
Project.dll!Program.MyThreadProc() Line 47 C#
... Thread.ThreadMain_ThreadStart() Line 93 C#
... ExecutionContext.RunInternal(...) Line 167 C#
[Native to Managed Transition]
kernel32.dll!BaseThreadInitThunk() Unknown
ntdll.dll!RtlUserThreadStart() Unknown

Оба примера наглядно демонстрируют, что стек начинается вместе с user-mode потоком в недрах операционной системы, .NET добавляет свои фреймы сверху, а отладчик видит на всю глубину (при наличии отладочных символов).

Heap

Известна также как управляемая куча (managed heap).

  • Здесь динамически выделяется и освобождается память под всё, что не получилось разместить на стеке или в регистрах процессора.

  • Выделение памяти в куче описывается словом аллокация.

  • Аллокации бывают явными (оператор new) и неявными (boxing и [CompilerGenerated] объекты, возникающие при обессахаривании замыканий, итераторов, async-методов).

  • Освобождение неиспользуемой памяти автоматическое — в CLR встроен сборщик мусора (garbage collector, GC).

  • На самом деле, GC занимается всеми аспектами автоматического управления памятью — запрашивает у операционной системы “сырые” регионы (через VirtualAlloc или mmap), выделяет место под объекты, делит объекты на поколения, собирает статистику, принимает решения, когда и в каких поколениях искать недостижимые объекты, утилизирует и дефрагментирует неиспользуемое пространство.

    “Garbage collection is simulating a computer with an infinite amount of memory" — Raymond Chen

    Konrad Kokosa подробно рассказывает про внутренности .NET GC. А ещё он смог прикрутить свой собственный коллектор (очень интересно!)

  • Слово “управляемая” добавляют именно потому, что .NET-куча всецело находится под управлением GC.

  • Куч может быть много. Есть Large Object Heap и Pinned Object Heap. В некоторых режимах GC заводит по куче на каждое ядро процессора. Но поскольку объекты в разных кучах могут ссылаться друг на друга, можно называть кучей всю их совокупность.

  • Вместе с каждым объектом хранятся ObjHeader и MethodTable pointer.

    ObjHeader используется для кеширования hash code, синхронизации через Monitor и разных флагов.

    MethodTable pointer — это указатель на метаданные. Благодаря ему каждый объект в куче самодостаточен, знает о своём конкретном типе, что в свою очередь делает возможным динамическое приведение типов, вызов виртуальных методов и Runtime Reflection.

    Подробнее: Managed object internals Part 1 & Part 2.