Ответ
Управление памятью в C# основано на разделении на стек (stack) и управляемую кучу (managed heap), а также автоматической сборке мусора (Garbage Collection, GC).
1. Стек (Stack)
- Хранит: Локальные переменные значимых типов (Value Types) —
int,bool,struct, а также ссылки (указатели) на объекты в куче. - Работа: Выделение и освобождение памяти происходит быстро и детерминировано, следуя принципу LIFO (Last-In-First-Out). Память освобождается сразу при выходе из области видимости (метода, блока кода).
- Пример:
void MyMethod() { int number = 42; // 'number' и его значение живут в стеке Person personRef; // Ссылка (4/8 байт) живет в стеке, сам объект — нет. } // При выходе из метода память для 'number' и 'personRef' мгновенно освобождается.
2. Управляемая куча (Managed Heap)
- Хранит: Объекты ссылочных типов (Reference Types) — экземпляры
class,string, массивы. - Работа: Память выделяется при создании объекта (
new). Освобождением занимается сборщик мусора (GC). Доступ к объекту осуществляется по ссылке из стека. - Пример:
void MyMethod() { // Ссылка 'person' живет в стеке. // Сам объект Person (с полями Name, Age) создается в управляемой куче. Person person = new Person { Name = "Alice", Age = 30 }; }
3. Сборка мусора (Garbage Collection) GC автоматически освобождает память в куче, когда объекты становятся недостижимыми (на них больше нет активных ссылок).
- Поколения (Generations): Куча разделена на три поколения (Gen 0, Gen 1, Gen 2) для оптимизации.
- Gen 0: Новые, недолгоживущие объекты. Сборка происходит часто и быстро.
- Gen 1: Объекты, пережившие одну сборку Gen 0.
- Gen 2: Долгоживущие объекты (например, статические данные, кэши). Сборка происходит реже, но она самая дорогая.
- Большой объектный куч (LOH): Объекты больше ~85 КБ размещаются в LOH, который собирается только во время сборки Gen 2.
4. Практические следствия для разработчика
- Утечки памяти в .NET возникают, когда на объекты неявно сохраняются ссылки (через события, статические коллекции, кэши).
- Для работы с неуправляемыми ресурсами (файлы, сокеты) реализуйте интерфейс
IDisposableи используйте конструкциюusing.using (var fileStream = new FileStream("file.txt", FileMode.Open)) { // работа с потоком } // Dispose() вызовется автоматически, даже при исключении - Для высокопроизводительных сценариев используйте
Span<T>иMemory<T>для работы с памятью на стеке или в пулах, минимизируя аллокации в куче.