VB.net Garbage collector ne pas divulguer des objets
Tout d'abord, merci d'avance pour votre aide.
J'ai décidé de demander de l'aide dans les forums comme celui-ci parce que, après plusieurs mois de dur travail, je ne pouvais pas trouver une solution à mon problème.
Ce qui peut être décrit comme " Pourquoi un objet créé dans VB.net n'est pas libéré par le GC, il est disposé, même lorsque le GC a été forcé d'être lancé?"
Veuillez considérer le morceau de code suivant. Évidemment, mon projet est beaucoup plus complexe, mais j'ai réussi à isoler le problème:
Imports System.Data.Odbc
Imports System.Threading
Module Module1
Sub Main()
'Declarations-------------------------------------------------
Dim connex As OdbcConnection 'Connection to the DB
Dim db_Str As String 'ODBC connection String
'Sentences----------------------------------------------------
db_Str = "My ODBC connection String to my MySQL database"
While True
'Condition: Infinite loop.
connex = New OdbcConnection(db_Str)
connex.Open()
connex.Close()
'Release created objects
connex.Dispose()
'Force the GC to be launched
GC.Collect()
'Send the application to sleep half a second
System.Threading.Thread.Sleep(500)
End While
End Sub
End Module
Simule une application multithread l'établissement de connexions à une base de données MySQL. Comme vous pouvez le voir, la connexion est créée comme un nouvel objet, puis relâchés. Enfin, le GC a été forcé d'être lancé. J'ai vu cet algorithme dans plusieurs forums, mais aussi dans l'aide en ligne MSDN, pour autant que je suis concerné, je ne suis pas en faire quelque chose de mal.
Le problème commence lorsque l'application est lancée. L'objet créé est disposé à l'intérieur du code, mais après un certain temps, la mémoire disponible est épuisée et l'application se bloque.
Bien sûr, ce problème est difficile de voir dans cette petite version, mais sur le projet réel, l'application manque de mémoire très rapidement (en raison de la quantité de connexions sur le temps) et comme résultat, le temps de disponibilité est à seulement deux jours. Alors j'ai besoin de redémarrer l'application à nouveau.
J'ai installé un profileur de mémoire sur ma machine (Scitech .Net Memory profiler 4.5, téléchargeable version d'essai ici). Il y a une section appelée "Enquêter sur les fuites de mémoire'. J'ai été vraiment étonné quand j'ai vu cela sur le "Temps Réel" de l'onglet. Si je suis correct, ce graphique est de me dire qu'aucun des objets créés sur le code ont été effectivement publié:
La surprise fut encore plus grande quand j'ai vu cette autre écran. D'après cela, tous les undisposed objets sont Système.Les Transactions type de, ce qui je suppose sont gérés à l'interne au sein de la .Bibliothèques Net que je ne suis pas à la création de tout objet de ce type sur mon code. Signifie-t-il il y a un bug sur le VB.net bibliothèques Standard???:
Veuillez noter que, dans mon code, je ne suis pas de l'exécution d'une requête. Si je le fais, le ODBCDataReader de l'objet ne pourra pas être diffusé, même si je l'appelle le .Close() méthode (de manière assez surprenante, le nombre d'inédits des objets de ce type est exactement le même que les inédits des objets de type Système.Les Transactions)
Une autre chose importante est la déclaration de GC.Collect(). Il est utilisé par le profileur de mémoire pour actualiser les informations à afficher. Si vous le retirez du code, le profiler l'habitude de' mettre à jour le diagramme en temps réel correctement, vous donnant la fausse impression que tout est correct.
Enfin, si vous omettre l' connex.Open() déclaration, la capture d'écran #1 rendra une ligne plate (ce qui signifie que tous les objets créés ont été relâchés avec succès), mais malheureusement, nous ne pouvons pas faire une requête sur la base de données si la connexion n'a pas été ouvert.
Quelqu'un peut trouver une explication logique à cela et aussi, une solution de contournement pour libérer les objets?
Merci à tous les gens.
Nico
Salut Hans, merci pour votre réponse. J'ai ajouté quelques commentaires sur mon auto-réponse. Des acclamations.
OriginalL'auteur Nicolas | 2012-12-02
Vous devez vous connecter pour publier un commentaire.
Disposer a rien à voir avec la collecte des ordures. La collecte des ordures est exclusivement sur géré ressources (mémoire). Jetez n'a aucune incidence sur la mémoire à tout, et n'est pertinente que pour des ressources non managées (connexions de base de données, les descripteurs de fichiers, de ressources gdi, sockets... rien pas de mémoire). Le seulement relation entre les deux a à voir avec la façon dont un objet est finalisé, parce que de nombreux objets sont souvent mises en œuvre telles que de les jeter supprimer la finalisation et à la finalisation de leur présentation .Dispose(). Explicitement l'Élimination() d'un objet jamais cause d'être collecté1.
Appelant explicitement le garbage collector est presque toujours une mauvaise idée. .Net utilise un générationnelle garbage collector, et donc le principal effet de l'appel vous-même, c'est que vous tiendrez sur la mémoire plus, car en forçant la collecte à l'heure, vous êtes susceptible de vérifier les articles avant qu'ils soient éligibles pour la collecte à tous, qui les envoie dans un ordre supérieur génération qui est perçue moins souvent. Ces éléments, autrement, aurait séjourné dans la baisse de production et éligible à la collection lors de la GC prochaine couru sur son propre. Vous pouvez avoir besoin d'utiliser GC.Collect() maintenant pour le profiler, mais vous devriez essayer de le supprimer de votre code de production.
Vous parler de votre application s'exécute pendant deux jours avant de s'écraser, et ne sont pas de profilage (ou de présenter les résultats d') votre code de production, donc je pense aussi que le profiler est en partie à vous induire en erreur ici. Vous avez réduit le code pour quelque chose qui a produit un fuite de mémoire, mais je ne suis pas sûr que c'est la fuite de mémoire que vous voyez dans la production. C'est en partie à cause de la différence dans le temps de reproduire l'erreur, mais c'est aussi "l'instinct". Je le mentionne parce que certains de ce que je vais proposer, n'aurait pas de sens immédiatement à la lumière de vos résultats du profileur. Que de la route, je ne sais pas pour vous ce qui se passe avec votre perte de mémoire, mais je peux faire quelques suppositions.
La première hypothèse est que votre code réel a bloc try/catch. Une exception est levée... peut-être pas sur toutes les connexions, mais parfois. Lorsque cela se produit, le bloc catch permet à votre programme de continuer à fonctionner, mais vous avez sauté sur l'
connex.Dispose()
ligne, et donc de les laisser ouvertes les connexions traîner. Ces connexions ont fini par créer un déni de service de la base de données, qui peut se manifester dans un certain nombre de façons. La correction est ici à assurez-vous de toujours utiliser un bloc finally pour tout ce que vous .Dispose(). Cela est vrai si vous avez actuellement un bloc try/catch, et c'est suffisamment important pour que je dirais que le code que vous avez posté jusqu'à présent est fondamentalement mal: vous avez besoin d'un try/finally. Il existe un raccourci pour ce, par l'intermédiaire d'unusing
bloc.La prochaine deviner, c'est que certains de vos commandes de fin d'assez grande, peut-être avec de grandes chaînes ou de l'image (byte[]) données concernées. Dans ce cas, les éléments finissent sur un garbage collector de la génération dite du Tas d'Objets Volumineux (LOH). La liturgie des heures est rarement collectées, et presque jamais compacté. Pense de compactage comme analogue à ce qui se passe quand vous défragmenter un disque dur. Si vous avez des articles allant de la liturgie des heures, vous pouvez vous retrouver dans une situation où la mémoire physique est lui-même libéré (collectées), mais le espace d'adressage au sein de votre processus (vous êtes normalement limité à 2 go) n'est pas libéré (compacté). Vous avez des trous dans votre espace d'adressage de mémoire qui ne sera pas récupéré. La mémoire RAM physique disponible pour votre système pour d'autres processus, mais avec le temps, cela génère toujours le même genre de dépassement de mémoire exception que vous vous voyez. La plupart du temps ce n'est pas grave: la plupart des .Les programmes sont de courte durée des utilisateurs des applications ou des ASP.Net les applications où tout le fil peut être déchirée vers le bas une fois qu'une page est servi. Puisque vous êtes en train de construire quelque chose comme un service qui devrait fonctionner pendant des jours, vous devez être plus prudent. Le correctif peut impliquer de manière significative re-travailler un peu de code, afin d'éviter de créer des objets volumineux. Que peut signifier la ré-utilisation d'un seul ou un petit ensemble de tableaux d'octets, ou en utilisant les techniques de streaming au lieu de concaténation de chaîne ou les constructeurs que pour les très grandes requêtes sql ou sql de la requête de données. Il peut également signifier que vous trouverez ce plus facile de le faire en tant que tâche planifiée qui fonctionne tous les jours et s'arrête à la fin de la journée, ou un programme qui est appelée sur demande.
Une dernière hypothèse est que quelque chose que vous êtes en train de faire, vos objets de connexion toujours, d'une certaine façon accessible par votre programme. Les gestionnaires d'événements sont une source fréquente d'erreurs de ce genre, mais je trouve ça étrange d'avoir des gestionnaires d'événement sur vos connexions, surtout que ce n'est pas une partie de votre exemple.
1 je suppose que je pourrais imaginer un scénario qui permettrait de faire ceci se produire. Un moyen simple serait de construire un objet suppose une collection mondiale pour tous les objets de ce type... les objets de la collection lors de la construction et de supprimer eux-mêmes à l'élimination. De cette façon, l'objet n'a pas pu être recueillies avant la mise au rebut, car avant de ce point, il serait toujours accessible... mais ce serait un très imparfaite de la conception du programme.
Dispose
. Une fois un tel objet estDispose
d, il sera admissible pour la collecte; jusqu'à ce qu'il est éliminé, il ne sera pas, à moins ou jusqu'à ce que l'éditeur d'événement en lui-même devient admissible.Bonjour [supercat]. Pas sûr de ce que tu veux dire. Je n'avais pas de mettre en œuvre tous les événements dans l'exemple que j'ai posté. L'application est un pur DOS de la console (donc pas de Windows Forms bibliothèques ont été chargés). Merci quand même.
OriginalL'auteur Joel Coehoorn
Merci à tous les gars pour vos réponses utiles.
Joel, vous avez raison. Ce code génère une "fuite" qui n'est pas forcément la même que la "fuite" problème que j'ai sur mon projet, bien qu'ils reproduisent les mêmes symptômes, c'est le nombre d'objets inédits continuer à grandir (et éventuellement d'échappement de la mémoire) sur le code mentionné ci-dessus. Donc je me demande quel est le problème avec elle que tout semble être codées de façon appropriée. Je ne comprends pas pourquoi ils ne sont pas éliminés/recueillies. Mais selon le profiler, ils sont toujours dans la mémoire et, éventuellement, permettra d'éviter de créer de nouveaux objets.
L'un de vos suppositions au sujet de mon "vrai" projet frappé le clou sur la tête. J'ai réalisé que mon "attraper" des blocs de ne pas l'appeler pour objet l'élimination, et ce qui est maintenant corrigé. Merci pour vos précieuses suggestions. Cependant, j'ai mis en place le 'l'aide de la" clause dans le code dans mon exemple ci-dessus et n'a pas fait de résoudre le problème.
Hans, vous êtes également en droit. Après la publication de la question, j'ai changé les bibliothèques sur le code ci-dessus pour faire les connexions à MySQL.
Les anciennes bibliothèques (dans l'exemple):
Les nouvelles bibliothèques:
Avec les nouveaux, le profileur rendu une ligne plate, sans plus de changements sur le code, c'est ce que j'ai été à la recherche après. Donc, mon conclussion est la même que la vôtre, c'est il y a peut être une erreur interne dans les anciennes qui fait que la chose arrive, ce qui en fait un véritable "fauteur de troubles'.
Maintenant je me souviens que je l'ai d'abord utilisé les nouvelles sur mon projet (le Système.Les données et Microsoft.Les données.Odbc) mais j'ai vite changé pour les anciens (le Système.Les données.Odbc) parce que les nouvelles ne permet pas l'activation de Plusieurs jeux d'enregistrements (MARS) ouvert. Mon application est une énorme quantité de requêtes sur la base de données MySQL, mais malheureusement, le nombre de connexions sont limitées. J'ai donc d'abord mis en place mon vrai code de telle façon qu'il fait seulement un peu de connexions, mais ils ont été partagés à travers le code (en passant de la connexion entre les fonctions en paramètre). C'était génial parce que (par exemple) j'avais besoin de récupérer un jeu d'enregistrements (disons les clients), et de faire beaucoup de contrôles dans le même temps (par exemple, le client dispose d'au moins une facture, le client dispose d'un double adresse e-mail, etc, ce qui implique beaucoup de côté les requêtes). Avec les "anciens" des bibliothèques, de la même connexion autorisé à créer plusieurs commandes et exécuter des requêtes différentes.
La "nouvelle" les bibliothèques ne permettent pas de MARS. Je ne peux créer une commande (c'est-à-dire, pour exécuter une requête) par session/connexion. Si j'ai besoin d'exécuter un autre, j'ai besoin de fermer le précédent jeu (qui n'est en fait pas possible comme je suis une itération sur elle), et puis pour faire de la nouvelle requête.
J'ai eu à trouver l'équilibre entre les deux problèmes. Donc je me retrouve à l'aide de la "nouvelle bibliothèques", à cause des problèmes de mémoire, et j'ai réécrit ma demande de ne pas partager les connexions (de sorte que chaque procédure en créer un nouveau en cas de besoin), ainsi que de réduire le nombre de connexions de l'application en même temps pour ne pas épuiser le pool de connexion.
La solution est loin d'être idéale car elle introduit la logique fausse sur l'application (le cas idéal, le scénario serait de migrer vers SQL server), mais il me donne de meilleurs résultats et de la demande est plus stable, au moins dans les premiers stades de la nouvelle version.
Merci encore pour vos suggestions, j'espère que vous trouverez les mines usefult trop.
Acclamations.
Nico
OriginalL'auteur Nicolas