Original : http://www.mikel.cn/article.asp?id=1698
Connaissez-vous le mécanisme de récupération de place de .Net ? Pouvez-vous décrire brièvement le fonctionnement de GC ? Comment gérer efficacement la mémoire ? Quel est le rôle de l’objet instancié dans le corps de l’instruction Using ?
Cette section est organisée comme suit : 1. Types de réseau et allocation de mémoire 2. Comment fonctionne le garbage collector GC 3. Que sont les ressources non gérées 4. Comment libérer efficacement les ressources d'objet. Résumé. Commençons maintenant notre étude de cette section.
1..Types de réseau et allocation de mémoire
Tous les types dans Net sont dérivés (directement ou indirectement) du type System.Object.
Les types dans CTS sont divisés en deux catégories : les types de référence (types de référence, également appelés types gérés), qui sont alloués sur le tas de mémoire, et les types de valeur. Les types de valeur sont alloués sur la pile. Comme le montre l'image
Les types de valeur sont sur la pile, premier entré, dernier sorti. Les variables de type valeur ont un ordre de vie. Cela garantit que les variables de type valeur libéreront des ressources avant qu'elles ne soient hors de portée. Plus simple et plus efficace que les types de référence. La pile alloue la mémoire des adresses hautes aux adresses basses.
Le type de référence est alloué sur le tas géré (Managed Heap), et une variable est déclarée et enregistrée sur la pile. Lorsque new est utilisé pour créer un objet, l'adresse de l'objet est stockée dans cette variable. Au contraire, le tas géré alloue la mémoire des adresses basses aux adresses hautes, comme le montre la figure
2. Comment fonctionne le ramasse-miettes GC
Dans la figure ci-dessus, lorsque le dataSet expire, nous n'affichons pas la destruction de l'objet et les objets sur le tas continuent d'exister, en attendant le recyclage GC.
Il est recommandé, mais pas obligatoire, que le garbage collector prenne en charge le vieillissement des objets tout au long de la génération. Une génération est une unité d'objets avec des âges relatifs en mémoire. Objet
Le nom de code ou l'âge identifie la génération à laquelle appartient l'objet. Dans le cycle de vie de l'application, les objets créés plus récemment appartiennent à une génération plus récente et ont des valeurs plus élevées que les objets créés précédemment.
Numéro de sous-code inférieur. Le code objet de la génération la plus récente est 0.
Lors de la création d'un nouvel objet, vous devez d'abord rechercher dans la liste chaînée libre pour trouver le bloc de mémoire le plus approprié, l'allouer, ajuster la liste chaînée des blocs de mémoire et fusionner les fragments. La nouvelle opération peut être effectuée en un temps presque O(1), en ajoutant 1 au pointeur supérieur du tas. Le principe de fonctionnement est le suivant : lorsque l'espace restant sur le tas géré est insuffisant ou que l'espace du générateur 0 est plein, le GC s'exécute et commence à récupérer de la mémoire. Au début du garbage collection, le GC compresse et ajuste la mémoire du tas, et les objets sont concentrés en haut. GC occupera une certaine quantité de temps CPU lors de l'analyse des déchets. L'algorithme GC d'origine analyse en réalité l'intégralité du tas, ce qui est inefficace. Le GC actuel divise les objets du tas en trois générations. L'entrée la plus récente dans le tas est la génération 0, suivie de la génération 1 et de la génération 2. Le premier GC analyse uniquement la génération 0. Si l'espace récupéré est suffisant pour l'utilisation actuelle, il n'est pas nécessaire d'analyser les objets des autres générations. Par conséquent, GC crée des objets plus efficacement que C++ et n’a pas besoin d’analyser tout l’espace du tas. L'amélioration des performances apportée par la stratégie d'analyse et la stratégie de gestion de la mémoire est suffisante pour compenser le temps CPU occupé par le GC.
3. Que sont les ressources non gérées ?
Une ressource non gérée courante est un objet qui encapsule une ressource du système d'exploitation, telle qu'un fichier, une fenêtre ou une connexion réseau. Pour de telles ressources, bien que le garbage collector puisse suivre la durée de vie de l'objet qui encapsule la ressource non gérée, il sait comment le faire. nettoyer ces ressources. Heureusement, la méthode Finalize() fournie par .net Framework permet de nettoyer correctement les ressources non gérées avant que le garbage collector ne recycle ces ressources. Voici plusieurs ressources non gérées courantes : pinceaux, objets de flux, objets de composants et autres ressources (Object, OdbcDataReader, OleDBDataReader, Pen, Regex, Socket, StreamWriter, ApplicationContext, Brush,
Composant, ComponentDesigner, Conteneur, Contexte, Curseur, FileStream,
Police, Icône, Image, Matrice, Minuterie, Info-bulle). (Se référer à MSDN)
4. Comment libérer efficacement les ressources non gérées.
Le GC ne peut pas gérer les ressources non gérées, alors comment libérer les ressources non gérées ? .Net propose deux méthodes :
(1) Destructeur : lorsque le garbage collector recycle les ressources d'un objet non géré, il appellera la méthode de finalisation de l'objet Finalize() pour nettoyer les ressources. Cependant, en raison des limitations des règles de fonctionnement du GC, le GC appelle la méthode Finalize de l'objet. méthode La ressource ne sera pas libérée une seule fois et l'objet sera supprimé après le deuxième appel.
(2) Héritez de l'interface IDisposable et implémentez la méthode Dispose(). L'interface IDisposable définit un modèle (avec prise en charge au niveau du langage), fournit un certain mécanisme pour libérer les ressources non gérées et évite les problèmes inhérents au garbage collection des destructeurs de périphériques. -questions liées.
Afin de mieux comprendre le mécanisme de garbage collection, j'ai spécialement écrit une partie du code et ajouté des commentaires détaillés. Définissez une seule classe FrankClassWithDispose (hérite de l'interface IDisposable), FrankClassNoFinalize (pas de finaliseur), FrankClassWithDestructor (définit le destructeur).
Le code spécifique est le suivant :
Code
1 utilisant le système ;
2 en utilisant System.Collections.Generic ;
3 en utilisant System.Text ;
4 en utilisant System.Data ;
5 en utilisant System.Data.Odbc ;
6 en utilisant System.Drawing ;
7 // Codé par Frank Xu Lei 18/2/2009
8 // Étudier la gestion de la mémoire .NET
9 // Garbage Collector garbage collector. Les ressources hébergées peuvent être récupérées en cas de besoin conformément aux politiques,
10 // Mais le GC ne sait pas gérer les ressources non gérées. Tels que la connexion réseau, la connexion à la base de données, le pinceau, le composant, etc.
11 //Deux mécanismes pour résoudre le problème de libération des ressources non gérées. Destructeur, interface IDispose
12 // Nombre de références COM
13 // Gestion manuelle C++, Nouvelle Suppression
14 // Gestion automatique VB
15 espaces de noms Gestion de la mémoire
16 {
17 // Hériter de l'interface IDisposable, implémenter la méthode Dispose et libérer les ressources de l'instance de FrankClassDispose
18 classe publique FrankClassWithDispose : IDisposable
19 {
20 privé OdbcConnection _odbcConnection = null ;
vingt-et-un
22 // Constructeur
23 public FrankClassWithDispose()
vingt-quatre {
25 si (_odbcConnection == null )
26 _odbcConnection = new OdbcConnection();
27 Console.WriteLine( " FrankClassWithDispose a été créé " );
28 }
29 //Méthode d'essai
30 public vide DoSomething()
31 {
32
33 /**/ /// /code ici pour faire quelque chose
34 retour;
35}
36 // Implémenter Supprimer et libérer les ressources utilisées par cette classe
37 public void Disposer()
38 {
39 si (_odbcConnection != null )
40 _odbcConnection.Dispose();
41 Console.WriteLine( " FrankClassWithDispose a été supprimé " );
42 }
43}
44 // Finalize n'est pas implémenté, attendez que GC recycle les ressources de l'instance de FrankClassFinalize et recyclez-les directement lorsque GC est en cours d'exécution.
45 cours public FrankClassNoFinalize
46 {
47 privé OdbcConnection _odbcConnection = null ;
48 //Constructeur
49 public FrankClassNoFinalize()
50 {
51 si (_odbcConnection == null )
52 _odbcConnection = new OdbcConnection();
53 Console.WriteLine( " FrankClassNoFinalize a été créé " );
54 }
55 //Méthode d'essai
56 public void DoSomething()
57 {
58
59 // GC.Collect();
60 /**/ /// /code ici pour faire quelque chose
61 retour ;
62 }
63}
64 // Implémentez le destructeur, compilez-le dans la méthode Finalize et appelez le destructeur de l'objet
65 // Lorsque GC est en cours d'exécution, la ressource n'est pas libérée lors du premier appel mais uniquement lors du deuxième appel.
66 //Ressources d'instance de FrankClassDestructor
67 // Le CLR utilise un thread indépendant pour exécuter la méthode Finalize de l'objet. Les appels fréquents dégraderont les performances.
68 classe publique FrankClassWithDestructor
69 {
70 privé OdbcConnection _odbcConnection = null ;
71 //Constructeur
72 public FrankClassWithDestructor()
73 {
74 si (_odbcConnection == null )
75 _odbcConnection = new OdbcConnection();
76 Console.WriteLine( " FrankClassWithDestructor a été créé " );
77 }
78 //Méthode d'essai
79 public void DoSomething()
80 {
81 /**/ /// /code ici pour faire quelque chose
82
83 retour ;
84}
85 // Destructeur, libère des ressources non gérées
86 ~ FrankClassAvecDestructeur()
87 {
88 si (_odbcConnection != null )
89 _odbcConnection.Dispose();
90 Console.WriteLine( " FrankClassWithDestructor a été supprimé " );
91 }
92 }
93}
94
Une instance de l'objet non géré OdbcConnection est utilisée. Le client construit a été brièvement testé. Le code client est le suivant :
Code
1 utilisant le système ;
2 en utilisant System.Collections.Generic ;
3 en utilisant System.Text ;
4 en utilisant System.Data ;
5 en utilisant MemoryManagement ;
6 // Codé par Frank Xu Lei 18/2/2009
7 // Étudier la gestion de la mémoire .NET
8 // Testez les objets non gérés récupérés.
9 // Tests pour code non managé, comparaison
10 // Pour le code managé, GC peut le recycler lui-même de manière plus stratégique, ou il peut implémenter IDisposable, appeler la méthode Dispose() et le publier activement.
11 espace de noms MemoryManagementClient
12 {
13 programmes de cours
14 {
15 static void Main (string [] arguments)
16 {
17
18 /**/ /////////////////////////////////////// //(1 ) / ///////////////////////////////////////// //
19 //Appelez la méthode Dispose() pour libérer activement. ressources, flexibilité
20 FrankClassWithDispose _frankClassWithDispose = null ;
21 essayer
vingt-deux {
23 _frankClassWithDispose = new FrankClassWithDispose();
24 _frankClassWithDispose.DoSomething();
25
26}
27 enfin
28 {
29 si (_frankClassWithDispose != null )
30 _frankClassWithDispose.Dispose();
31 // Console.WriteLine("L'instance FrankClassWithDispose a été publiée");
32}
33
34 /**/ /////////////////////////////////////// //(2 ) / //////////////////////////////////////////// /
35 // Vous pouvez utiliser l'instruction Using pour créer un objet non géré avant la fin de l'exécution de la méthode, il sera appelé.
36 en utilisant (FrankClassWithDispose _frankClassWithDispose2 = new FrankClassWithDispose())
37 {
38 // _frankClassWithDispose2.DoSomething();
39 }
40
41 /**/ /////////////////////////////////////// //(3 ) / ///////////////////////////////////////// //
42 //Lorsque le garbage collector est en cours d'exécution, les ressources sont libérées une fois
43 FrankClassNoFinalize _frankClassNoFinalize = new FrankClassNoFinalize();
44 _frankClassNoFinalize.DoSomething();
45
46 /**/ ////////////////////////////////////////// (4) //////////////////////////////////////////// / /
47 // Lorsque le garbage collector est en cours d'exécution, il faut deux fois pour libérer les ressources.
48 FrankClassWithDestructor _frankClassWithDestructor = new FrankClassWithDestructor();
49 _frankClassWithDestructor.DoSomething();
50 /**/ ////////////////////////////////////////// / (5 ) //////////////////////////////////////////// /
51 // L'instruction Using ne peut pas être utilisée pour créer un objet car elle n'implémente pas l'interface IDispose.
52 // utilisation (FrankClassWithDestructor _frankClassWithDestructor2 = new FrankClassWithDestructor())
53 // {
54 // _frankClassWithDestructor2.DoSomething();
55 // }
56
57 /**/ ////////////////////////////////////////// /// /////////////////////////////////////// //
58 // Pour le débogage
59 Console.WriteLine( " Appuyez sur n'importe quelle touche pour continuer " );
60 Console.ReadLine();
61
62
63}
64}
65 }
66
Parfois, les ressources doivent être libérées à un moment précis. Une classe peut implémenter l'interface IDisposable qui exécute les méthodes de gestion des ressources et de tâches de nettoyage IDisposable.Dispose.
Si l'appelant doit appeler la méthode Dispose pour nettoyer l'objet, la classe doit implémenter la méthode Dispose dans le cadre du contrat. Le garbage collector n'appelle pas par défaut
Méthode Dispose ; cependant, l’implémentation de la méthode Dispose peut appeler des méthodes dans le GC pour réguler le comportement final du garbage collector.
Il convient de mentionner que : l'appel de la méthode Dispose() libère activement des ressources et est flexible. Vous pouvez utiliser l'instruction Using pour créer des objets non gérés avant la fin de l'exécution de la méthode.
La méthode Dispose() libère des ressources. L'effet des deux extrémités du code est le même. Vous pouvez afficher l'IL compilé.
Code
1. essayez
2 {
3 IL_0003 : non
4 IL_0004 : instance newobj void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
5 IL_0009 : stloc.
6 IL_000a : ldloc 0.
7 IL_000b : instance callvirt void [MemoryManagement]MemoryManagement.FrankClassWithDispose::DoSomething()
8 IL_0010 : non
9 IL_0011 : non
10 IL_0012 : congé.s IL_0028
11 } // fin .try
12 enfin
13 {
14 IL_0014 : non
15 IL_0015 : ldloc 0.
16 IL_0016 : ldnull
17 IL_0017 : ceq
18 IL_0019 : stloc.s 4 0000$ CS
19 IL_001b : ldloc.s 4 0000 $ CS
20 IL_001d : brtrue.s IL_0026
21 IL_001f : ldloc 0.
22 IL_0020 : instance de callvirt void [MemoryManagement]MemoryManagement.FrankClassWithDispose::Dispose()
23 IL_0025 : non
24 IL_0026 : non
25 IL_0027 : finfinalement
26 } // gestionnaire de fin
27 IL_0028 : non
28 IL_0029 : instance newobj void [MemoryManagement]MemoryManagement.FrankClassWithDispose::.ctor()
29 IL_002e : stloc.1
30. essayez
31 {
32 IL_002f : non
33 IL_0030 : non
34 IL_0031 : quitter.s IL_0045
35 } // fin .try
36 enfin
37 {
38 IL_0033 : ldloc 1.
39 IL_0034 : ldnull
40 IL_0035 : ceq
41 IL_0037 : stloc.s 4 0000 $ CS
42 IL_0039 : ldloc.s 4 0000 $ CS
43 IL_003b : brtrue.s IL_0044
44 IL_003d : ldloc 1.
45 IL_003e : instance de callvirt void [mscorlib]System.IDisposable::Dispose()
46 IL_0043 : non
47 IL_0044 : finfinalement
48 } // gestionnaire de fin
49
L'instruction Using a le même effet pour libérer des ressources d'objet non gérées. Ceci est souvent rencontré lors des entretiens, par exemple sur les utilisations du mot-clé Using et des questions similaires. La réponse idéale de base est qu'en plus de référencer l'espace de noms et de définir des alias pour l'espace de noms, cette utilisation réalise le recyclage des ressources d'objets non gérés comme le bloc try final. Juste une façon simple de l’écrire.
Lorsque vous utilisez la méthode Dispose pour libérer un objet non géré, vous devez appeler GC.SuppressFinalize. Si l'objet est dans la file d'attente de finalisation, GC.SuppressFinalize empêchera le GC d'appeler la méthode Finalize. Parce que l’appel de la méthode Finalize sacrifiera certaines performances. Si votre méthode Dispose a déjà nettoyé les ressources déléguées, le GC n'a pas besoin d'appeler à nouveau la méthode Finalize (MSDN) de l'objet. Vous trouverez ci-joint le code MSDN pour votre référence.
Code
classe publique BaseResource : IDisposable
{
//Pointez vers des ressources externes non gérées
descripteur IntPtr privé ;
// Autres ressources gérées utilisées par cette classe.
Composants de composants privés ;
// Suivre si la méthode .Dispose est appelée, bit d'indicateur, contrôler le comportement du garbage collector
private bool disposé = false ;
//Constructeur
ressource de base publique()
{
// Insérez ici le code constructeur approprié.
}
// Implémente l'interface IDisposable.
// Ne peut pas être déclaré comme méthode virtuelle virtual.
// Les sous-classes ne peuvent pas remplacer cette méthode.
public void Disposer()
{
Disposer( vrai );
// Quitte la file d'attente de finalisation
//Définit le code finaliseur bloquant de l'objet
//
GC.SuppressFinalize( this );
}
// Dispose(bool disposing) est exécuté dans deux situations différentes.
// Si disposer est égal à true, la méthode a été appelée
// Ou indirectement appelé par le code utilisateur. Le code managé et non managé peut être publié.
// Si disposer est égal à false, la méthode a été appelée en interne par le finaliseur,
// Vous ne pouvez pas référencer d'autres objets, seules les ressources non gérées peuvent être libérées.
protected virtual void Dispose (bool disposition)
{
// Vérifiez si Dispose a été appelé.
si ( ! ceci .disposé)
{
// Si égal à true, libère toutes les ressources gérées et non gérées
si (disposer)
{
// Libère les ressources gérées.
Composants.Dispose();
}
// Libère les ressources non gérées, si la disposition est fausse,
// Seul le code suivant sera exécuté.
FermerHandle(poignée);
handle = IntPtr.Zero ;
// Notez que ce n'est pas thread-safe.
// Une fois la ressource gérée libérée, d'autres threads peuvent être démarrés pour détruire l'objet.
// Mais avant que l'indicateur supprimé ne soit défini sur true
// Si la sécurité des threads est requise, le client doit l'implémenter.
}
disposé = vrai ;
}
//Utiliser l'interopérabilité pour appeler la méthode
// Efface les ressources non gérées.
[System.Runtime.InteropServices.DllImport( " Kernel32 " )]
private extern static Boolean CloseHandle (descripteur IntPtr);
//Utilisez le destructeur C# pour implémenter le code du finaliseur
// Ceci ne peut être appelé et exécuté que si la méthode Dispose n'a pas été appelée.
// Si vous donnez à la classe de base une chance de finaliser.
// Ne fournit pas de destructeur pour les sous-classes.
~BaseResource()
{
// Ne recréez pas le code de nettoyage.
// Sur la base de considérations de fiabilité et de maintenabilité, appeler Dispose(false) est le meilleur moyen
Disposer( false );
}
// Permet d'appeler plusieurs fois la méthode Dispose,
// Mais une exception sera levée si l'objet a été libéré.
// Peu importe le moment où vous traitez l'objet, vous vérifierez si l'objet est libéré.
// vérifie s'il a été supprimé.
public void DoSomething()
{
si (ce .disposé)
{
lancer une nouvelle ObjectDisposedException();
}
}
Pour les types pour lesquels l’appel de la méthode Close est plus naturel que la méthode Dispose, vous pouvez ajouter une méthode Close à la classe de base.
La méthode Close ne prend aucun paramètre et appelle la méthode Dispose qui effectue le travail de nettoyage approprié.
L'exemple suivant illustre la méthode Close.
// Ne définit pas la méthode sur virtual.
// Les classes héritées ne sont pas autorisées à remplacer cette méthode
public void Fermer()
{
// Appelez le paramètre Dispose sans paramètres.
Disposer();
}
public static void Main()
{
//Insérez le code ici pour créer
// et utilise un objet BaseResource.
}