Подтвердить что ты не робот

Поведение GC при закреплении объекта

При просмотре кода PinnableObjectCache от mscorlib я столкнулся с следующим кодом:

for (int i = 0; i < m_restockSize; i++)
{
    // Make a new buffer.
    object newBuffer = m_factory();

    // Create space between the objects.  We do this because otherwise it forms 
    // a single plug (group of objects) and the GC pins the entire plug making 
    // them NOT move to Gen1 and Gen2. By putting space between them
    // we ensure that object get a chance to move independently (even if some are pinned).  
    var dummyObject = new object();
    m_NotGen2.Add(newBuffer);
}

Мне стало интересно, что означает ссылка на вилку? При попытке привязать объект в памяти не будет ли адрес GC указывать конкретный адрес, указанный для объекта? Каково это поведение plug, которое действительно делает и почему существует необходимость "пробела" между объектами?

4b9b3361

Ответ 1

Итак, после нескольких попыток получить официальные ответы от людей с "внутренними знаниями", я решил немного поэкспериментировать.

То, что я попытался сделать, - это повторное создание сценария, в котором у меня есть несколько закрепленных объектов и некоторые незакрепленные объекты между ними (я использовал byte[]), чтобы попытаться создать эффект, при котором незакрепленные объекты не перемещаются более высокое поколение внутри кучи GC.

Код работал на моем ноутбуке Intel Core i5, в 32-битном консольном приложении под управлением Visual Studio 2015 как в Debug, так и в Release. Я отлаживал код в реальном времени с помощью WinDBG.

Код довольно прост:

private static void Main(string[] args)
{
    byte[] byteArr1 = new byte[4096];
    GCHandle obj1Handle = GCHandle.Alloc(byteArr1 , GCHandleType.Pinned);
    object byteArr2 = new byte[4096];
    GCHandle obj2Handle = GCHandle.Alloc(byteArr2, GCHandleType.Pinned);
    object byteArr3 = new byte[4096];
    object byteArr4 = new byte[4096];
    object byteArr5 = new byte[4096];
    GCHandle obj4Handle = GCHandle.Alloc(byteArr5, GCHandleType.Pinned);
    GC.Collect(2, GCCollectionMode.Forced);
}

Я начал с рассмотрения адресного пространства кучи GC с помощью !eeheap -gc:

generation 0 starts at 0x02541018 
generation 1 starts at 0x0254100c
generation 2 starts at 0x02541000 

ephemeral segment allocation context: none

segment      begin      allocated   size 
02540000     02541000   02545ff4    0x4ff4(20468)

Теперь я просматриваю код и смотрю, как объекты распределяются:

0:000> !dumpheap -type System.Byte[]
Address     MT          Size
025424e8    72101860    4108     
025434f4    72101860    4108     
02544500    72101860    4108     
0254550c    72101860    4108     
02546518    72101860    4108  

Глядя на адреса, я вижу, что они все в настоящее время находятся в генерации 0, начиная с 0x02541018. Я также вижу, что объекты закреплены с помощью !gchandles:

Handle     Type      Object      Size    Data Type  
002913e4   Pinned    025434f4    4108    System.Byte[]
002913e8   Pinned    025424e8    4108    System.Byte[]

Теперь я перехожу через код до тех пор, пока не получу строку, которая запускает GC.Collect:

0:000> p
eax=002913e1 ebx=0020ee54 ecx=00000002 edx=00000001 esi=025424d8 edi=0020eda0
eip=0062055e esp=0020ed6c ebp=0020edb8 iopl=0  nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b  efl=00000206
0062055e e80d851272      call    mscorlib_ni+0xa28a70 (GC.Collect) (72748a70)

И теперь, предвидя, что произойдет, я снова проверю адрес генерации GC, используя !eeheap -gc, и я вижу следующее:

Number of GC Heaps: 1
generation 0 starts at 0x02547524
generation 1 starts at 0x0254100c
generation 2 starts at 0x02541000

Начальный адрес для генерации 0 был перемещен с 0x02541018 на 0x02547524. Теперь я проверяю адрес закрепленных и не закрепленных объектов byte[]:

0:000> !dumpheap -type System.Byte[]
Address  MT           Size
025424e8 72101860     4108     
025434f4 72101860     4108     
02544500 72101860     4108     
0254550c 72101860     4108     
02546518 72101860     4108   

И я вижу, что у них все остались по одному и тому же адресу. Но, то, что начало 0 начинается с 0x02547524, означает, что все они были переведены в поколение 1.

Затем, я помню, что читал что-то об этом поведении в книге Pro.NET Performance, он утверждает следующее:

Привязка объекта предотвращает его перемещение мусором коллектор. В модели поколений он предотвращает продвижение закрепленных объектов между поколениями. Это особенно важно в молодые поколения, такие как поколение 0, поскольку размер генерация 0 очень мала. Прикрепленные объекты, которые вызывают фрагментацию в пределах поколения 0 имеют потенциал причинения большего вреда, чем он может возникнуть из-за того, что мы рассмотрели прикрепление, прежде чем мы представили поколения в картину. Fortunatly, CLR обладает способностью продвигать закрепленные объекты, используя следующий трюк: если поколение 0 становится сильно фрагментированные с закрепленными объектами, CLR может объявить все пространство поколения 0 считается более высоким поколением и выделить новые объекты из новой области памяти, которые станут генерация 0. Это достигается путем изменения эфемерного сегмента.

И это на самом деле объясняет поведение, которое я вижу внутри WinDBG.

Итак, чтобы заключить и до тех пор, пока у кого-нибудь не будет другого объяснения, я думаю, что комментарий неверен и на самом деле не отражает то, что действительно происходит внутри GC. Если у кого-то есть что-то, что нужно разработать, я был бы рад добавить.