346 votes

Pourquoi est-ce une mauvaise pratique d'appeler System.gc() ?

Après répondant à une question sur la façon de objets sans force en Java (le gars effaçait un HashMap de 1,5GB) avec System.gc() on m'a dit que c'était une mauvaise pratique d'appeler System.gc() manuellement, mais les commentaires n'ont pas été entièrement convaincants. En outre, personne n'a semblé oser mettre une note supérieure ou inférieure à ma réponse.

On m'y a dit que c'était une mauvaise pratique, mais on m'a aussi dit que les exécutions du ramasseur d'ordures n'arrêtent plus systématiquement le monde, et qu'il pourrait aussi effectivement être utilisé par la JVM uniquement comme un indice, donc je suis un peu perdu.

Je comprends que la JVM sait généralement mieux que vous quand elle doit récupérer de la mémoire. Je comprends également que s'inquiéter de quelques kilo-octets de données est stupide. Je comprends aussi que même les mégaoctets de données ne sont plus ce qu'ils étaient il y a quelques années. Mais quand même, 1,5 gigaoctets ? Et vous connaître il y a environ 1,5 Go de données qui traînent en mémoire ; ce n'est pas comme si c'était un coup dans le vide. Est-ce que System.gc() systématiquement mauvais, ou y a-t-il un point à partir duquel cela devient acceptable ?

Donc la question est en fait double :

  • Pourquoi est-ce ou non une mauvaise pratique d'appeler System.gc() ? S'agit-il vraiment d'un simple indice pour la JVM dans certaines implémentations, ou s'agit-il toujours d'un cycle de collecte complet ? Existe-t-il vraiment des implémentations de garbage collector qui peuvent faire leur travail sans arrêter le monde ? Merci de nous éclairer sur les diverses affirmations faites dans les commentaires de mon article. réponse .
  • Où est le seuil ? Est-ce que c'est jamais une bonne idée d'appeler System.gc() ou y a-t-il des moments où c'est acceptable ? Si oui, quels sont ces moments ?

4 votes

Je pense qu'un bon moment pour appeler System.gc() est lorsque vous faites quelque chose qui est déjà un long processus de chargement. Par exemple, je travaille sur un jeu et je prévois d'appeler System.gc() lorsque le jeu charge un nouveau niveau, à la fin du processus de chargement. L'utilisateur attend déjà un peu, et l'augmentation supplémentaire des performances peut en valoir la peine ; mais je vais également placer une option dans l'écran de configuration pour désactiver ce comportement.

1 votes

Un autre cas a été expliqué dans java-monitor.com/forum/showthread.php?t=188 Où il est expliqué que ce peut être une mauvaise pratique d'appeler la fonction System.gc()

0 votes

Juste mes deux centimes après avoir lu certaines des réponses (et il faut noter que je suis assez nouveau à Java, donc s'il vous plaît prenez mon opinion avec un grain de sel), mais... Je pense que la meilleure question pourrait être "pourquoi avez-vous un si grand HashMap en premier lieu ?"

251voto

Steven Schlansker Points 17463

La raison pour laquelle tout le monde dit toujours qu'il faut éviter System.gc() est que c'est un assez bon indicateur d'un code fondamentalement défectueux . Tout code qui en dépend pour l'exactitude est certainement défectueux ; tout code qui en dépend pour les performances est très probablement défectueux.

Vous ne savez pas sous quel type de ramasseur de déchets vous travaillez. Il y en a certainement qui ne "arrêtez le monde" comme vous l'affirmez, mais certaines JVM ne sont pas aussi intelligentes ou pour diverses raisons (peut-être sont-elles sur un téléphone ?) ne le font pas. Vous ne savez pas ce qu'il va faire.

De plus, il n'est pas garanti qu'il fasse quoi que ce soit. La JVM peut tout simplement ignorer votre requête.

La combinaison de "vous ne savez pas ce que cela va faire", "vous ne savez même pas si cela va aider" et "vous ne devriez pas avoir besoin de l'appeler de toute façon" est la raison pour laquelle les gens disent avec tant de force que généralement vous ne devriez pas l'appeler. Je pense que c'est un cas de "si vous avez besoin de demander si vous devriez l'utiliser, vous ne devriez pas".


EDIT pour répondre à quelques préoccupations exprimées dans l'autre fil de discussion :

Après avoir lu le fil de discussion dont vous avez donné le lien, il y a quelques points supplémentaires que j'aimerais souligner. Tout d'abord, quelqu'un a suggéré qu'appeler gc() peut rendre la mémoire au système. Ce n'est pas nécessairement vrai - le tas Java lui-même croît indépendamment des allocations Java.

En d'autres termes, la JVM conservera de la mémoire (plusieurs dizaines de mégaoctets) et fera croître le tas si nécessaire. Elle ne renvoie pas nécessairement cette mémoire au système même lorsque vous libérez des objets Java ; elle est parfaitement libre de conserver la mémoire allouée pour l'utiliser lors de futures allocations Java.

Pour montrer qu'il est possible que System.gc() ne fait rien, regarde :

http://bugs.sun.com/view_bug.do?bug_id=6668279

et en particulier qu'il y a un -XX:DisableExplicitGC Option VM.

1 votes

Vous pourriez être en mesure de construire une configuration bizarre de type Rube Goldberg dans laquelle la méthode d'exécution de la GC affecte l'exactitude de votre code. Peut-être que cela masque une interaction étrange entre les threads, ou peut-être qu'un finaliseur a un effet significatif sur l'exécution du programme. Je ne suis pas tout à fait sûr que ce soit possible, mais ça pourrait l'être, alors je me suis dit que je devais le mentionner.

2 votes

@zneak vous pourriez par exemple avoir mis du code critique dans les finaliseurs (ce qui est fondamentalement du code cassé).

0 votes

Pour ce qui est de ma réponse directe au point 2, je dirais que la règle est que cela ne vaut généralement pas la peine de perdre son temps. Mais il y a des exceptions à toutes les règles. J'essaie simplement d'expliquer le processus de réflexion. Sur une JVM Sun récente, cela ne causera probablement pas beaucoup de tort, mais lorsque vous commencerez à passer à d'autres JVM, il se peut que cela ne fasse plus ce que vous attendez. C'est pourquoi je l'éviterais en général.

150voto

Stephen C Points 255558

Il a déjà été expliqué qu'appeler system.gc() mai ne font rien, et que tout code qui "a besoin" du ramasseur de déchets pour fonctionner est cassé.

Cependant, la raison pragmatique pour laquelle il n'est pas pratique d'appeler System.gc() est qu'il est inefficace. Et dans le pire des cas, c'est terriblement inefficace ! Laissez-moi vous expliquer.

Un algorithme GC typique identifie les ordures en parcourant tous les objets non ordonnés du tas et en déduisant que tout objet non visité doit être une ordure. À partir de là, nous pouvons modéliser le travail total d'une collecte de déchets comme étant une partie proportionnelle à la quantité de données vivantes et une autre partie proportionnelle à la quantité de déchets. work = (live * W1 + garbage * W2) .

Supposons maintenant que vous fassiez ce qui suit dans une application monofilaire.

System.gc(); System.gc();

Le premier appel fera (selon nos prévisions) (live * W1 + garbage * W2) travail, et se débarrasser des déchets en suspens.

Le deuxième appel fera (live* W1 + 0 * W2) travail et ne rien réclamer. En d'autres termes, nous avons fait (live * W1) travail et n'a absolument rien obtenu .

Nous pouvons modéliser l'efficacité du collecteur comme la quantité de travail nécessaire pour collecter une unité de déchets, c'est-à-dire efficiency = (live * W1 + garbage * W2) / garbage . Donc, pour rendre la GC aussi efficace que possible, nous devons maximiser la valeur de garbage lorsque nous exécutons la GC ; c'est-à-dire attendre que le tas soit plein. (Et aussi, faire en sorte que le tas soit aussi grand que possible, mais c'est un sujet distinct).

Si l'application n'intervient pas (en appelant System.gc() ), la GC attendra que le tas soit plein avant de s'exécuter, ce qui permet une collecte efficace des déchets. 1 . Mais si l'application force le GC à s'exécuter, il y a de fortes chances que le tas ne soit pas plein, et le résultat sera que la collecte des ordures est inefficace. Et plus l'application force le GC, plus le GC devient inefficace.

Remarque : l'explication ci-dessus ne tient pas compte du fait qu'un GC moderne typique divise le tas en "espaces", que le GC peut étendre dynamiquement le tas, que l'ensemble d'objets non résiduels de l'application peut varier, etc. Néanmoins, le même principe de base s'applique à tous les vrais garbage collectors. 2 . Il est inefficace de forcer le GC à fonctionner.


1 - C'est ainsi que fonctionne le collecteur de "débit". Les collecteurs simultanés tels que CMS et G1 utilisent des critères différents pour décider quand lancer le collecteur d'ordures.

2 - J'exclue également les gestionnaires de mémoire qui utilisent exclusivement le comptage de références, mais aucune implémentation actuelle de Java n'utilise cette approche ... pour une bonne raison.

47 votes

+1 Bonne explication. Notez cependant que ce raisonnement ne s'applique que si vous vous souciez du débit. Si vous voulez optimiser la latence à des points spécifiques, forcer GC peut avoir du sens. Par exemple (hypothétiquement parlant), dans un jeu, vous pourriez vouloir éviter les retards pendant les niveaux, mais vous ne vous souciez pas des retards pendant le chargement des niveaux. Il serait alors logique de forcer le GC après le chargement du niveau. Cela diminue le débit global, mais ce n'est pas ce que vous optimisez.

3 votes

@sleske - ce que vous dites est vrai. Cependant, l'exécution du GC entre les niveaux est une solution de fortune ... et ne résout pas le problème de latence si les niveaux sont suffisamment longs pour que vous ayez besoin d'exécuter le GC pendant un niveau de toute façon. Une meilleure approche consiste à utiliser un ramasse-miettes concurrent (à faible pause) ... si la plate-forme le supporte.

0 votes

Je ne suis pas tout à fait sûr de ce que vous voulez dire par "a besoin du garbage collector pour fonctionner". Les conditions que les applications doivent éviter sont : les échecs d'allocation qui ne peuvent jamais être satisfaits et une surcharge élevée du GC. Faire des appels aléatoires à System.gc() entraîne souvent une surcharge élevée du GC.

50voto

KitsuneYMG Points 7604

Beaucoup de gens semblent vous dire de ne pas faire ça. Je ne suis pas d'accord. Si, après un gros processus de chargement comme le chargement d'un niveau, vous croyez que :

  1. Vous avez beaucoup d'objets qui sont inaccessibles et qui n'ont peut-être pas été gc'ed. et
  2. Vous pensez que l'utilisateur pourrait supporter un petit ralentissement à ce stade.

il n'y a aucun mal à appeler System.gc(). Je le vois comme le c/c++ inline mot-clé. C'est juste un indice pour le gc que vous, le développeur, avez décidé que le temps/les performances ne sont pas aussi importants qu'ils le sont habituellement et qu'une partie pourrait être utilisée pour récupérer de la mémoire.

Le conseil de ne pas compter sur le fait qu'il fasse quelque chose est correct. Ne comptez pas sur le fait qu'il fonctionne, mais donner l'indication que c'est un moment acceptable pour collecter est tout à fait correct. Je préfère perdre du temps à un moment du code où cela n'a pas d'importance (écran de chargement) plutôt que lorsque l'utilisateur interagit activement avec le programme (comme pendant un niveau d'un jeu).

Il y a un moment où je vais force collection : lorsque l'on tente de savoir si un objet particulier fuit (qu'il s'agisse de code natif ou d'une interaction de callback importante et complexe. Oh et tout composant de l'interface utilisateur qui jette ne serait-ce qu'un coup d'œil à Matlab). Ceci ne devrait jamais être utilisé dans un code de production.

4 votes

+1 pour GC tout en analysant les fuites de mémoire. Notez que l'information sur l'utilisation du tas (Runtime.freeMemory() et al.) n'est vraiment significative qu'après avoir forcé une GC, sinon elle dépendrait du moment où le système a pris la peine d'exécuter une GC pour la dernière fois.

4 votes

il n'y a aucun mal à appeler System.gc() cela pourrait nécessiter stop the world approche et c'est un réel préjudice, si cela se produit

7 votes

Il y a est Il n'y a pas de mal à appeler le ramasseur de déchets explicitement. Appeler le GC au mauvais moment gaspille des cycles CPU. Vous (le programmeur) ne disposez pas de suffisamment d'informations pour déterminer le bon moment... mais la JVM le fait.

33voto

JT. Points 116

Les gens ont fait un bon travail en expliquant pourquoi ne pas l'utiliser, je vais donc vous parler de quelques situations où vous devriez l'utiliser :

(Les commentaires suivants s'appliquent à Hotspot fonctionnant sous Linux avec le collecteur CMS, où je suis sûr de pouvoir dire que System.gc() déclenche en fait toujours un ramassage complet des déchets).

  1. Après le travail initial de démarrage de votre application, vous pouvez être dans un état terrible d'utilisation de la mémoire. La moitié de votre génération permanente pourrait être pleine de déchets, ce qui signifie que vous êtes d'autant plus proche de votre premier CMS. Dans les applications où cela a de l'importance, ce n'est pas une mauvaise idée d'appeler System.gc() pour "réinitialiser" votre tas à l'état initial de données vivantes.

  2. Dans le même ordre d'idées que le point 1, si vous surveillez de près l'utilisation du tas, vous voulez avoir une lecture précise de l'utilisation de base de la mémoire. Si les deux premières minutes du temps de fonctionnement de votre application sont consacrées à l'initialisation, vos données vont être faussées à moins que vous ne forciez (ahem... "suggériez") le gc complet dès le départ.

  3. Vous pouvez avoir une application qui est conçue pour ne jamais promouvoir quoi que ce soit à la génération permanente pendant qu'elle est en cours d'exécution. Mais vous avez peut-être besoin d'initialiser certaines données à l'avance qui ne sont pas assez importantes pour être automatiquement déplacées vers la génération permanente. À moins que vous n'appeliez System.gc() après que tout ait été configuré, vos données pourraient rester dans la nouvelle génération jusqu'à ce que le moment soit venu de les promouvoir. Tout d'un coup, votre application à très faible latence et à faible GC subit une pénalité de latence ÉNORME (relativement parlant, bien sûr) pour avoir promu ces objets pendant les opérations normales.

  4. Il est parfois utile de disposer d'un appel System.gc dans une application de production pour vérifier l'existence d'une fuite de mémoire. Si vous savez que l'ensemble des données vivantes au moment X doit exister dans un certain rapport avec l'ensemble des données vivantes au moment Y, il peut être utile d'appeler System.gc() au moment X et au moment Y et de comparer l'utilisation de la mémoire.

1 votes

Pour la plupart des collecteurs d'ordures générationnels, les objets de la nouvelle génération doivent survivre à un certain nombre (souvent configurable) de collectes d'ordures. System.gc() une fois pour forcer la promotion d'objets ne vous rapporte rien. Et vous ne voulez sûrement pas appeler System.gc() huit fois de suite et prier que maintenant, la promotion a été faite et que les coûts économisés d'une promotion ultérieure justifient les coûts de plusieurs GC complets. Selon l'algorithme de la GC, la promotion d'un grand nombre d'objets peut même ne pas entraîner de coûts réels, car elle réaffecte simplement la mémoire à l'ancienne génération ou à la copie simultanée

9voto

Thomas Pornin Points 36984

L'efficacité de la GC repose sur un certain nombre d'heuristiques. Par exemple, une heuristique courante est que les accès en écriture aux objets se produisent généralement sur des objets qui ont été créés il y a peu de temps. Une autre est que de nombreux objets ont une durée de vie très courte (certains objets seront utilisés pendant longtemps, mais beaucoup seront éliminés quelques microsecondes après leur création).

Appel à System.gc() c'est comme donner un coup de pied au GC. Cela signifie : "tous ces paramètres soigneusement réglés, ces organisations intelligentes, tous les efforts que vous venez de déployer pour allouer et gérer les objets de manière à ce que tout se passe bien, eh bien, laissez tout tomber et recommencez à zéro". C'est mai améliorer les performances, mais la plupart du temps, cela ne fait que dégrade performance.

Pour utiliser System.gc() de manière fiable(*), vous devez connaître le fonctionnement du GC dans ses moindres détails. Ces détails ont tendance à changer considérablement si vous utilisez une JVM d'un autre fournisseur, ou la version suivante du même fournisseur, ou la même JVM mais avec des options de ligne de commande légèrement différentes. C'est donc rarement une bonne idée, à moins que vous ne vouliez traiter un problème spécifique dans lequel vous contrôlez tous ces paramètres. D'où la notion de "mauvaise pratique" : ce n'est pas interdit, la méthode existe, mais elle est rarement payante.

(*) Je parle ici d'efficacité. System.gc() ne sera jamais rupture un programme Java correct. Il ne fera pas non plus appel à de la mémoire supplémentaire que la JVM n'aurait pas pu obtenir autrement : avant de lancer une erreur de type OutOfMemoryError la JVM fait le travail de System.gc() même si c'est en dernier recours.

1 votes

+1 pour avoir mentionné que System.gc() n'empêche pas l'erreur OutOfMemoryError. Certaines personnes le croient.

1 votes

En fait, il mai éviter l'erreur OutOfMemoryError à cause de la gestion des références souples. Les SoftReferences créées après la dernière exécution du GC ne sont pas collectées dans l'implémentation que je connais. Mais c'est un détail d'implémentation sujet à changement à tout moment et une sorte de bogue et rien sur lequel vous devriez compter.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X