J'essaie de mieux comprendre le concept des fuites de mémoire. Quelqu'un peut-il indiquer des informations utiles qui peuvent m'aider à mieux comprendre exactement ce que sont les fuites de mémoire et comment je les trouverais dans mon code.
Il existe de nombreux types de fuites de mémoire, mais en général, le terme fait référence à une sorte de ressource qui n'est plus utilisée, mais qui occupe toujours de la mémoire. Si vous en avez plusieurs, votre application prend beaucoup de mémoire et vous finissez par en manquer.
En C #, voici quelques fuites de mémoire courantes:
Dispose()
sur tous les objets IDisposable
. tilisez l'instruction using
.Une fuite de mémoire traditionnelle se produit lorsque vous allouez de la mémoire, puis "oubliez" en quelque sorte de la libérer. Dans l'ancien code C++, cela signifie appeler new
sans delete
correspondant. En C, cela signifiait un appel à alloc()
/malloc()
sans free()
correspondant.
Dans .Net, vous n'obtenez pas de fuites de mémoire au sens traditionnel du terme, car vous n'êtes pas censé libérer de la mémoire vous-même. Au lieu de cela, vous comptez sur le garbage collector pour le libérer pour vous. Cependant, cela ne signifie pas que vous ne perdrez jamais la mémoire. Il existe plusieurs façons de conserver accidentellement une référence qui empêche le ramasse-miettes de faire son travail. Ceux-ci incluent des variables globales (en particulier des listes, des dictionnaires et d'autres types de collection qui peuvent être utilisés pour "mettre en cache" des objets), des gestionnaires d'événements accrochés à une référence d'objet, des références d'historique récursives et le tas d'objets volumineux.
Il est également important de noter ici qu'un schéma d'augmentation de l'utilisation de la mémoire dans .Net ne signifie pas nécessairement que votre application perd de la mémoire. En cas de faible pression globale de la mémoire, le garbage collector pourrait plutôt choisir de gagner du temps en ne collectant pas encore, ou en collectant uniquement dans l'espace d'adressage existant du processus sans retourner la mémoire au système d'exploitation.
Une très bonne lecture est Tout le monde pense à la collecte des ordures dans le mauvais sens .
En général, une fuite de mémoire, ou toute fuite de ressources, se produit chaque fois que le programme alloue de la mémoire (ou toute autre ressource), puis omet de la désallouer une fois terminée. Dans les applications natives, la fuite de mémoire est la fuite de ressources la plus courante et peut se produire lorsque la référence de ressource (le pointeur vers le bloc alloué) sort du domaine et est détruite, mais la ressource allouée (le bloc de mémoire) n'est pas détruite. Dans ce cas, la ressource (mémoire) est perdue car le programme a perdu la capacité de la libérer, même s'il le souhaite, car il ne se souvient plus de l'emplacement de la ressource (l'adresse du bloc).
Dans les applications gérées, les fuites de mémoire sont un peu plus délicates. Étant donné que le runtime peut suivre automatiquement les références aux ressources, il peut également comprendre lorsqu'une ressource (un objet) n'est plus référencée par une partie active de l'application (il n'y a pas de chaîne de références d'un cadre de pile vers cette ressource sur n'importe quel thread) et ainsi le runtime peut comprendre quand il est sûr de collecter les objets qui ne font plus référence par l'application. Donc, dans un monde géré, une "fuite" se produirait lorsque vous croyez que l'applicaiton ne fait plus référence à un objet (et donc qu'il peut être collecté par le runtime) mais qu'en fait, à travers une chaîne de références, vous ont une référence et ne peuvent donc pas être collectées.
Je recommande fortement l'article de Raymond Chen lié ci-dessus, est très très éclairant.
Lorsque de la mémoire est affectée à une application, l'application a l'obligation de libérer cette mémoire sur le système d'exploitation afin qu'elle puisse être réutilisée par d'autres applications. Une fuite de mémoire se produit lorsqu'une application ne libère pas cette mémoire, empêchant ainsi sa réallocation.
Pour le code managé, le garbage collector suit les références aux objets créés par une application. Dans la plupart des situations, le CLR traitera l'allocation et la désallocation de mémoire de manière transparente et raisonnable au nom du processus en cours. Cependant, les développeurs .NET doivent toujours considérer la gestion des ressources car il existe encore des situations dans lesquelles la mémoire peut fuir malgré le travail du garbage collector.
Considérez le code suivant:
Widget widget = new Widget();
La ligne de code ci-dessus crée une nouvelle instance de la classe Widget et le champ widget se voit attribuer une référence à cet objet. GC garde une trace des références associées à chaque objet et désalloue la mémoire des objets pour lesquels il n'y a pas de références fortes.
Il convient de mentionner que le garbage collection du CLR ne collectera que les objets gérés, le code .NET peut et utilise fréquemment des ressources non managées qui ne peuvent pas être récupérées automatiquement.
Des fuites de ressources non gérées se produisent lorsque l'objet pour lequel ces ressources ont été allouées ne parvient pas à les désallouer correctement avant que la dernière référence à ces ressources ne soit hors de portée, ce qui laisse les ressources allouées, mais non référencées et donc inutilisables pour l'application.
Les classes qui référencent directement les ressources non gérées doivent garantir que ces ressources sont correctement désallouées. Un exemple de cela ressemblerait à ceci:
public void ManagedObject : IDisposable
{
//A handle to some native resource.
int* handle;
public ManagedObject()
{
//AllocateHandle is a native method called via P/Invoke.
handle = AllocateHandle();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
//deal with managed resources here
FreeHandle(handle);
}
}
~ManagedType()
{
Dispose(false);
}
}
Le paramètre disposing
est faux lorsqu'il est appelé à partir d'un finaliseur. Il s'agit d'empêcher l'utilisation des ressources gérées depuis le finaliseur car les références gérées doivent être considérées comme non valides à ce stade.
Notez également que la méthode Dispose()
appelle GC.SuppressFinalize(this)
ce qui empêche le finaliseur de s'exécuter pour cette instance. Cela est dû au fait que les ressources qui auraient été désallouées dans le finaliseur ont été désallouées dans l'appel Dispose, ce qui rend inutile un appel de fializer.
Le code client qui utilise des classes qui gèrent des ressources non managées (ou toute classe qui implémente IDisposable) doit le faire dans un bloc using
pour s'assurer que IDisposable.Dispose
Est appelé lorsque l'accès à la ressource n'est pas plus longtemps car cela prendra en charge les ressources gérées et non gérées et, dans le cas de l'exemple ci-dessus, garantira qu'aucun appel très coûteux au finaliseur ne sera effectué.
Des excuses pour ma randonnée. Je m'arrête maintenant.
"Fuite de mémoire" doit être définie comme "mémoire utilisée lorsque VOUS pensez qu'elle ne doit pas être utilisée" pour s'appliquer aux langages/runtimes récupérés en C #/Java.
Traditionnellement, une "fuite de mémoire" est définie comme une mémoire qui n'est pas correctement désallouée (voir le lien wikipedia dans d'autres réponses), ce qui ne se produit généralement pas pour les environnements de collecte des ordures. Notez qu'en raison de problèmes d'exécution, même les langues récupérées peuvent gaspiller de la mémoire - c'est-à-dire que même si JavaScript est une langue récupérée, il était facile de divulguer une grande quantité d'objets JavaScript dans l'exécution JavaScript d'Internet Explorer.
Une fuite de mémoire se produit lorsque votre programme alloue dynamiquement de la mémoire qui n'est pas correctement désallouée une fois que vous avez fini de l'utiliser. Si vous avez un programme qui fait cela en continu, votre fuite augmentera de plus en plus et très bientôt votre programme prendra toute votre RAM.