Pull to refresh

Устройство памяти в .NET



Существует много устойчивых заблуждений об устройстве памяти в .NET, которые кочуют из учебника в учебник, и из совета в совет.
Например, что вся память находится под управлением сборщика мусора. Увы, но это не так. Есть области памяти, которые находятся под управлением системы и которыми можно управлять лишь опосредовано и выделенная в них память НЕ освобождается.

В каждом домене приложений AppDomain память разделена на две части. Системная (на схеме она отмечена красной рамкой) и пользовательская (голубая рамка).
Системная область не допускает прямого управления пользователем, и память в ней освобождается только при завершении домена (за редким исключением).

Начнём с таблицы типов — она заполнена специальными объектами, наследниками RuntimeType, которые можно получить методом GetType(). Каждый класс в .NET состоит из статической и экземплярной части. При первом упоминании типа в выполняемом виртуальной машиной выражении его статическая часть размещается таблице. При этом для КАЖДОГО T новый тип Generic сохраняется отдельно. (И поэтому можно реализовать Singleton через статические поля и для каждого T будет существовать не более одного экземпляра Singleton).

public static class Singleton<T> where T : class, new()
{
    static Singleton()
    {
        // Здесь могут быть дополнительные условия на тип T
    }

    public static readonly T Instance = 
        typeof(T).InvokeMember(typeof(T).Name, 
                                BindingFlags.CreateInstance | 
                                BindingFlags.Instance |
                                BindingFlags.Public |
                                BindingFlags.NonPublic, 
                                null, null, null) as T;
}


И если домен активно использует внешние сборки: загружает, использует их типы и выгружает, то таблица типов медленно но верно растёт, что в перспективе может вызвать OutOfMemory.

Список блоков синхронизации — набор блоков синхронизации, который может расширяться системой в случае необходимости.

Строковой пул — хранит в себе интернированные строки. С ним связана забавная особенность — в него по умолчанию помещаются все строковые константы использованные в сборке. А так же в него можно помещать строки при помощи метода String.Itern. Так вот, один и тот же алгоритм пересечения отсортированных списков строк реализованый на C++, C# (без интернирования и с интернированием) дал следующие результаты, примем время работы программы на C++ за 1. Тогда C# без интернирования затратил 0.9, а C# с интернированием 0.65.

Пул потоков — хранилище потоков под управлением виртуальной машины. Если в Task'ах регулярно виснут потоки, то пул будет досоздавать новые (и разумеется никогда их не высвобождать).

Прочее — отображение системных ресурсов, некоторые заранее выделенные переменные (например OutOfMemoryException — память под которое выделяется при старте домена).

Пользовательская область памяти.

SOH — Small Object Heap — куча для хранения маленьких объектов (в текущих реализациях .NET для объектов меньше 85000 байт). Разделённая на 3 поколения. 0 (эфемерное поколение), 1 и 2 поколение объектов. Для каждого из поколений установлен лимит памяти, и после того как он будет достигнут — может быть запущен сборщик мусора. При сборке мусора объекты «утрамбовываются» для избежания фрагментации памяти.

LOH — Large Object Heap — куча для больших объектов (в текущих реализациях .NET больше 85000 байт). Эта куча не разделена на поколения и в неё не выполняется дефрагментация.

Stack — стек приложения. в нём выделяется память под структуры *те, которые используются без боксинга, и не являющиеся полями классов*, ссылки на экземпляры классов, fixed и stackalloc. Каждая ссылка на объект содержит три поля.
Сам указатель на экземпляр (4 байта на 32-битных машинах, и 8 на 64-битных).
Индекс блока синхронизации (4 байта. На самом деле часть битов в нём выделена под системные флаги, например флаг пометки объекта к удалению сборщиком мусора).
Указатель на RuntimeType в системной области памяти.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.