Как стать автором
Обновить

.NET Частичка правды о размещении объектов на Run-time

Время на прочтение5 мин
Количество просмотров6.3K
Как известно, в .NET память делится на две категории: стек (Stack) и управляемая куча (managed heap, далее просто куча). На стеке располагается ссылка(ObjectRef) на объект (ObjectInstance), который, в свою очередь, располагается в куче.

В данной статье речь пойдет о расположении объекта в куче.

Предполагается, что у читателя имеются знания о:
1. stack
2. managed heap
3. GC
4. Слабые ссылки (weak references)


ObjectInstance

ObjectInstance – структура данных (объект), находящаяся по адресу, на который указывает ObjectRef.
Экземпляры значимых типов могут храниться:
1. в стеке потока
2. в GC Heap.
Экземпляры ссылочных типов, в зависимости от их размера, могут храниться:
1. в Garbage Collector Heap (объекты размером до 85000 байт)
2. в Large Object Heap (объекты размером более 85000 байт)

Пример структуры экземпляра объекта

Рисунок 1. Пример структуры экземпляра объекта

Как видно из рисунка 1, ObjectInstance содержит следующие поля:
1. Object Header
2. Type Handle (рассмотрение данного поля выходит за рамки статьи)
3. Поля экземпляра объекта
4. Ссылки на строковые литералы

Object Header

Вновь взглянем на рисунок 1. Как видно, ObjectRef указывает на адрес, смещенный на 4 байта относительно начала ObjectInstance. Это сделано для оптимизации – в Object Header содержится информация, которая используется не так часто, как остальные поля экземпляра объекта.
В данном поле содержится номер ячейки в таблице Sync Block Entry Table. Номер отсчитывается от единицы. В случае если для ObjectInstance ни разу не использовался оператор lock или метод GetHashCode(), то в ячейке Object Header будет храниться 0.
Поскольку связывание таблицы Sync Block Entry Table и ObjectInstance осуществляется по индексу, CLR может свободно менять размер данной таблицы в памяти.
Ячейка таблицы Sync Block Entry Table, индекс которой содержится в Object Header, в свою очередь содержит слабую ссылку на этот же ObjectInstance – это сделано для того, что бы CLR могла отслеживать владельца этой ячейки (например, для синхронизации потоков).
Так же, эта ячейка содержит ссылку на объект SyncBlock в структуре SyncBlock List. SyncBlock содержит полезную но редко используемую информацию:
1. Информацию о шлюзовании (thunking)
2. Индекс AppDomain
3. Данные о блокировке объекта
4. Хэш-код

Рассмотрим пример. Дан код:
  1. class Sample
  2. {
  3.     TestClass sync = new TestClass();
  4.         
  5.     void Main()
  6.     {
  7.         lock(sync)
  8.         {
  9.             //операции, требующие синхронизации
  10.         }
  11.         sync.GetHashCode();
  12.     }
  13. }
* This source code was highlighted with Source Code Highlighter.


В строке 3, объект sync содержит в поле Object Header значение 0. Далее, в строке 7 значение поля меняется и становится отличным от нуля. Это происходит из-за того, что CLR создает объект SyncBlock в SyncBlock List и помещает индекс записи в Object Header. В строке 11 в объект SyncBlock помещается значение хэш-кода.
Рассмотрение остальных полей объекта SyncBlock выходит за рамки данной статьи.

Поля экземпляра объекта и строковые литералы

За Type Handle идет список полей экземпляра, длина которого бывает разной. По умолчанию поля экземпляра размещаются так, чтобы память использовалась эффективно, а промежутки между полями были минимальны.
В листинге 1 показан класс SimpleClass, где объявлено несколько переменных экземпляра разного размера.
Листинг 1
  1. class SimpleClass
  2. {
  3.     private byte b1 = 1;                // 1 байт
  4.     private byte b2 = 2;                // 1 байт
  5.     private byte b3 = 3;                // 1 байт
  6.     private byte b4 = 4;                // 1 байт
  7.     private char c1 = 'A';             // 2 байта
  8.     private char c2 = 'B';             // 2 байта
  9.     private short s1 = 11;             // 2 байта
  10.     private short s2 = 12;             // 2 байта
  11.     private int i1 = 21;                // 4 байта
  12.     private long l1 = 31;              // 8 байтов
  13.     private string str = "MyString";    // 4 байта (это лишь OBJECTREF)
  14.  
  15.     // Общий размер полей экземпляра - 28 байтов
  16.     static void Main()
  17.     {
  18.          SimpleClass simpleObject = new SimpleClass();
  19.         return;
  20.     }
  21. }
* This source code was highlighted with Source Code Highlighter.


Окно памяти для SimpleClass Object Instance

Рисунок 2. Окно памяти для SimpleClass Object Instance

На рис. 2 показано, как выглядит экземпляр объекта SimpleClass в окне просмотра содержимого памяти.
Перед открытием Debugger Memory Window (Debug->Windows->Memory->Memory1) точка прерывания была установлена на операторе return. Затем, по адресу simpleObject, содержащемуся в регистре ECX, определили адрес экземпляра объекта, дамп памяти для которого необходимо посмотреть.
1. Первый 4-байтовый блок — номер syncblk. Поскольку мы не писали код синхронизации для этого объекта (и не обращались к его хэш-коду), этот номер равен 0.
2. Ссылка на объект, хранящаяся в переменной simpleObject, размещенной в стеке, указывает на 4 байта, начало которых находится по смещению 4.
3. Далее идет размещение в памяти восьми байтового значения переменной l1.
4. Переменная str типа String представляется 4-байтовой ссылкой ObjectRef, указывающей на экземпляр строки, размещенной в GC Heap.
5. Переменная i1 типа int размещается следом, занимая 8 байт.
6. Две переменных типа char — c1 и c2 — располагаются «бок о бок».
7. Две переменных типа short — s1 и s2 — также располагаются «бок о бок».
8. Переменные b1, b2, b3 и b4 типа Byte упакованы в DWORD и располагаются «бок о бок».
По умолчанию члены объекта не обязательно размещаются в памяти в том порядке, в каком они объявлены в исходном коде.
Чтобы при размещении полей в памяти соблюдалась их лексическая последовательность, нужно применить атрибут StructLayoutAttribute. Атрибут принимает в качестве входного значения перечисление LayoutKind, имеющее следующие значения:
1. Sequential – Члены объекта располагаются последовательно, в порядке своего появления при экспортировании в неуправляемую память.
2. Explicit – Точное положение каждого члена объекта в неуправляемой памяти управляется явно. Каждый член должен использовать атрибут FieldOffsetAttribute для указания положения этого поля внутри типа.
3. Auto – Среда CLR автоматически выбирает соответствующее размещение для членов объекта в неуправляемой памяти. Доступ к объектам, определенным при помощи этого члена перечисления, не может быть предоставлен вне управляемого кода. Попытка выполнить такую операцию вызовет исключение.

Рассмотрев неструктурированное содержимое памяти, исследуем экземпляр объекта с помощью утилиты Son of Strike (SOS).
Переведя проект в режим unmanaged debug (Properties->Debug->Enable unmanaged code debugging), и открыв Immediate Window (ctrl + alt + I) выполним следующий команды:

  1. .load sos
  2. !DumpHeap -type SimpleClass
* This source code was highlighted with Source Code Highlighter.


В результате вывода получим содержимое всех куч и всех экземпляров заданного типа:

Результат команды «!DumpHeap -type SimpleClass»

Рисунок 3. Результат команды «!DumpHeap -type SimpleClass»

Как видно из рисунка 3, размер объекта составляет 36 байт. В эти 36 байт входят:
1. Поля экземпляра SimpleClass занимают 28 байтов (Длина строки не имеет значения, так как экземпляр SimpleClass содержит только ссылку на таблицу литералов, размером 4 байта.).
2. Остальные 8 байтов содержатся в Object Header (4 байта) и Type Handle (4 байта).

Мы узнали адрес экземпляра simpleObject, теперь давайте выведем дамп содержимого этого экземпляра командой «!DumpObj 0x0214ba30»:

Результат команды !DumpObj 0x0214ba30

Рисунок 4. Результат команды «!DumpObj 0x0214ba30»

Компилятор C# по умолчанию использует для членов классов размещение LayoutType.Auto, следовательно, загрузчик класса может расположить поля по своему усмотрению, чтобы минимизировать промежутки между полями.

Материалы:
MSDN
Теги:
Хабы:
+25
Комментарии22

Публикации

Изменить настройки темы

Истории

Работа

.NET разработчик
72 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн