Garbage Collector en .NET - Partie 3
2020-09-20 par Badre BSAILA
Dans les précédents articles, j’ai exposé quelques notions sur la gestion mémoire .NET notamment : le tas managé, LOH, GC, la collection, les générations et les ségments. J’ai aussi montré comment on les quantifie avec windbg. Dans cette 3ème partie, je vous explique comment le GC parcourt le graphe des objets et décide quel objet va être collecté.
L’article est la 3ème partie d’une série où je vais couvrir les bases de la gestion mémoire en .NET :
- Partie 1 - Tas managé, LOH, GC, Collection, Générations et Ségments.
- Partie 2 - Démonstration avec windbg.
- Partie 3 - Graphe des objets et références (article courant).
- Partie 4 - Workstation/Server GC et arrière-plan GC.
- Partie 5 - Compteurs de performance et événements ETW.
Graphe des objets
On peut imaginer la mémoire d’une application .NET comme un graphe (un arbre pour être plus spécifique) d’objets: chacun étant le parent s’il référence un objet et fils s’il est réferencé par un autre.
La mémoire d’une application desktop comme celle-ci :
Peut être schématisée ainsi :
Comme signalé sur la 1ère partie, le GC parcourt le graphe des objets pour déterminer quel objet va être collecté. On peut résumer l’opération comme suit :
- Sélectionner une racine pour débuter le parcours : l’objet fenêtre.
- Vérifier si l’objet sélectionné référence des objets : le bouton.
- Sélectionner l’objet bouton.
- Vérifier si l’objet référence des objets : nom et texte du bouton.
- Vérifier si ces derniers référencent des objets : non, fin du parcours.
- Si des objets existent dans la mémoire sans être référencés par aucune racine ou ses fils: les marquer comme éligible à la collecte.
Ainsi si on exécute l’instruction _button1 = null
, le GC va marquer les objets: bouton, nom du bouton et texte du bouton comme éligible à la collecte.
Les racines
Les racines sont des objets haut niveau d’une application .NET, ils peuvent être créés par le CLR ou l’application elle même (comme la fenêtre de départ d’une application Winforms). Une racine référence directement ou indirectement tous les objets créés pour son compte. On les désigne aussi par racines externes, par opposition aux racines internes qui sont des objets de générations supérieures référençants des objets de générations inférieures. Dans tous les cas, tant qu’une racine ou un objet parent est en vie, on ne pourra jamais collecter les objets fils qu’ils soient des fils directes ou indirectes.
Pour retrouver les racines d’un objet sur windbg, on peut utiliser la commande suivante :
!sos.gcroot -all <adresseObjet>
0:009> !sos.gcroot -all 000001eb336463b8
HandleTable:
000001EB334815C0 (pinned handle)
-> 000001EB4352AD08 System.Object[]
-> 000001EB336462A0 Newtonsoft.Json.JsonConverter[]
-> 000001EB336463B8 Newtonsoft.Json.Converters.KeyValuePairConverter
Found 1 roots.
Les références
Les références entre objets peuvent prendre diverses formes chacune ayant une certaine implication sur la gestion mémoire :
- Normale (Strong): est créée quand on référence un objet B dans le code de l’objet A, elle protège l’objet B d’être collecté tant que A est en vie.
- Faible (Short Weak): Une relation faible ne protège pas les fils, mais permet de toujours les évaluer. La référence sur un fils retournera null s’il est déjà collecté et dans la file de finalisation. On la crée en utilisant la classe System.WeakReference.
- WeakTrackResurrection (Long Weak): Semblable à une relation faible, la référence sur l’un des fils retournera l’objet même si ce dernier est déjà dans la file de finalisation.
- Référence compté: utilisée en interne par le CLR pour les interfaces COM.
- Épinglé (Pinned): une relation qui signale au GC que l’objet ne doit pas être déplacé lorsqu’on compacte le tas managé. Elle est utilisée pour des ressources non-managées comme les fichiers OS, réseaux, Api C/C++, Api Windows (Win32)/Linux…etc. La cause étant que CLR les gère avec des pointeurs, ce qui veut dire qu’on corrompra la mémoire si on les déplace.
- Asynchrone-épinglé (AsyncPinned): c’est un type de référence Pinned dédié pour les opérations asynchrones (sur les fichiers, réseaux, …). Ils permettent au CLR de les ‘unpinner’ immédiatement à la fin de l’opération asynchrone.
- Dépendance: une relation de dépendance crée une relation normale entre 2 objets séparés. On peut en créer avec la classe System.Runtime.CompilerServices.ConditionalWeakTable<TKey,TValue>.
Pour avoir des statistiques sur les références du tas :
!sos.gchandles
0:009> !gchandles
Handle Type Object Size Data Type
000001EB33481000 WeakShort 000001eb3370ea98 144 System.Net.Http.HttpConnection
000001EB33481008 WeakShort 000001eb3370a058 72 System.Threading.Thread
000001EB33481600 WeakLong 000001eb33591910 152 System.RuntimeType+RuntimeTypeCache
000001EB33481608 WeakLong 000001eb335914b0 152 System.RuntimeType+RuntimeTypeCache
000001EB33481200 Strong 000001eb33622090 72 System.Threading.Thread
000001EB33481208 Strong 000001eb3361ffe8 112 System.Object[]
000001EB334815F0 Pinned 000001eb33521348 24 System.Object
000001EB334815F8 Pinned 000001eb43521038 8184 System.Object[]
000001EB33481DF8 RefCounted 000001eb3361cb78 64 1 System.EventHandler`1[[Windows.Foundation.Diagnostics.TracingStatusChangedEventArgs, System.Private.CoreLib]]
000001EB334823F0 Dependent 000001eb33710e78 160 0000000000000000 System.Byte[][]
000001EB334823F8 Dependent 000001eb33639040 160 0000000000000000 System.Byte[][]
000001EB33481958 AsyncPinned 000001eb33705910 72 System.Threading.OverlappedData
000001EB33481968 AsyncPinned 000001eb33704c58 72 System.Threading.OverlappedData
[...]
Statistics:
MT Count TotalSize Class Name
00007ffc95bf0ae8 1 24 System.Object
00007ffc960b03a0 1 64 Interop+Kernel32+ConsoleCtrlHandlerRoutine
00007ffc95d58208 1 64 Interop+Winsock+LPLOOKUPSERVICE_COMPLETION_ROUTINE
00007ffc95cb45f0 1 128 System.ExecutionEngineException
00007ffc95cb44f0 1 128 System.StackOverflowException
00007ffc961736c8 1 176 System.Threading.Tasks.TplEventSource
00007ffc962838d8 2 320 System.Byte[][]
00007ffc95cb29d0 64 2048 System.Runtime.CompilerServices.GCHeapHash
00007ffc95d47b88 106 16112 System.RuntimeType+RuntimeTypeCache
00007ffc95bf6610 27 82240 System.Object[]
[...]
Total 338 objects
Handles:
Strong Handles: 100
Pinned Handles: 9
Async Pinned Handles: 20
Ref Count Handles: 1
Weak Long Handles: 142
Weak Short Handles: 64
Dependent Handles: 2
On peut toujours filtrer par type de relation !sos.gchandles -type Pinned
.
Les références sont stocké dans une table de références et sont reportés au GC durant la collection pour déterminer si un objet est toujours en vie. On peut en ajouter ou supprimer avec le type System.Runtime.InteropServices.GCHandle :
Références
- Article de Jeffrey Richter sur le cycle de vie des objets.
- Defrag Tools: #34 - CLR GC - Part 2, Channel 9.
- Defrag Tools: #35 - CLR GC - Part 3, Channel 9.
- Microsoft Docs.
Badre BSAILA, Ingénieur d'étude et développement .NET sénior