Comportement du GC lors du repérage d’un object

En parcourant le code de PinnableObjectCache de mscorlib , j’ai rencontré le code suivant:

 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); } 

Je me suis demandé ce que la référence à une fiche signifie? Lorsqu’il essaie d’épingler un object en mémoire, le CPG ne donne-t-il pas l’adresse spécifique spécifiée pour cet object? Qu’est-ce que ce comportement de plug fait réellement et pourquoi est-il nécessaire “d’espacer” les objects?

Ok, alors après plusieurs tentatives pour obtenir des réponses officielles de personnes ayant une “connaissance interne”, j’ai décidé d’expérimenter moi-même un peu.

Ce que j’ai essayé de faire est de reproduire le scénario dans lequel j’ai deux objects épinglés et quelques objects non épinglés entre eux (j’ai utilisé un byte[] ) pour essayer de créer un effet où les objects non épinglés ne se déplacent pas plus haut. génération à l’intérieur du tas GC.

Le code a été exécuté sur mon ordinateur portable Intel Core i5, à l’intérieur d’une application console 32 bits exécutant Visual Studio 2015, à la fois en mode débogage et version. J’ai débogué le code en direct avec WinDBG.

Le code est plutôt simple:

 private static void Main(ssortingng[] 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); } 

J’ai commencé par jeter un coup d’œil sur l’espace d’adressage de tas GC en utilisant !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) 

Maintenant, je passe en revue le code en cours d’exécution et observe les objects alloués:

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

En regardant les adresses, je vois qu’elles sont toutes actuellement à la génération 0 car elle commence à 0x02541018 . Je vois aussi que les objects sont épinglés avec !gchandles :

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

Maintenant, je passe dans le code jusqu’à ce que GC.Collect à la ligne qui exécute 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) 

Et maintenant, anticipant ce qui se passe, je vérifie à nouveau l’adresse de génération du GC en utilisant !eeheap -gc et je vois ce qui suit:

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

L’adresse de départ pour la génération 0 a été déplacée de 0x02541018 à 0x02547524 . Maintenant, je vérifie l’adresse des objects byte[] épinglés et non épinglés:

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

Et je vois qu’ils sont tous restés à la même adresse. Mais le fait que la génération 0 commence maintenant à 0x02547524 signifie qu’ils ont tous été promus à la génération 1.

Ensuite, je me souviens avoir lu quelque chose à propos de ce comportement dans le livre Pro .NET Performance , qui dit ceci:

Épingler un object l’empêche d’être déplacé par le ramasse-miettes. Dans le modèle générationnel, cela empêche la promotion d’objects épinglés entre générations. Cela est particulièrement important chez les jeunes générations, telles que la génération 0, car la taille de la génération 0 est très petite. Les objects épinglés qui entraînent une fragmentation au sein de la génération 0 risquent de causer plus de tort qu’il ne semblerait en examinant les objects épinglés avant l’introduction des générations dans l’image. Heureusement, le CLR est capable de promouvoir des objects épinglés à l’aide de l’astuce suivante: si la génération 0 devient gravement fragmentée avec des objects épinglés, le CLR peut déclarer tout l’espace de la génération 0 considéré comme une génération supérieure et allouer de nouveaux objects à partir d’une nouvelle région. de la mémoire qui deviendra la génération 0. Ceci est réalisé en changeant le segment éphémère.

Et cela explique en fait le comportement que je constate dans WinDBG.

Donc, pour conclure et jusqu’à ce que quiconque ait une autre explication, je pense que le commentaire n’est pas correct et ne rend pas vraiment compte de ce qui se passe réellement au sein du GC. Si quelqu’un a quelque chose à élaborer, je serais heureux de l’append.