web-dev-qa-db-fra.com

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

Après réponse une question sur la façon de objets sans force en Java (le gars effaçait un HashMap de 1,5 Go) avec System.gc(), on m'a dit que c'était une mauvaise pratique d'appeler System.gc() manuellement, mais les commentaires n'étaient pas entièrement convaincants. En outre, personne n'a semblé oser voter par le haut ni baisser ma réponse.

On m’a dit que c’était une mauvaise pratique, mais j’ai aussi appris que le nettoyage des ordures n’arrête plus systématiquement le monde et qu’il pourrait également être utilisé efficacement par la JVM à titre d’allusion. à perte.

Je comprends que la JVM sait généralement mieux que vous quand elle doit récupérer de la mémoire. Je comprends aussi que s’inquiéter de quelques kilo-octets de données est idiot. Je comprends aussi que même des mégaoctets de données ne sont pas ce qu’ils étaient il ya quelques années. Mais encore, 1,5 gigaoctet? Et vous savez qu'il y a environ 1,5 Go de données en mémoire; ce n'est pas comme si c'était un coup dans le noir. Est-ce que System.gc() est systématiquement mauvais, ou y a-t-il un point où tout devient correct?

La question est donc double:

  • Pourquoi est-ce ou n'est-ce pas une mauvaise pratique d'appeler System.gc()? S'agit-il vraiment d'un indice pour la JVM dans certaines mises en œuvre, ou s'agit-il toujours d'un cycle de collecte complet? Existe-t-il vraiment des implémentations de ramasse-miettes qui peuvent faire leur travail sans arrêter le monde? Merci de faire la lumière sur les différentes affirmations que les gens ont faites dans les commentaires de mon réponse .
  • Où est le seuil? Est-ce que jamais est une bonne idée d'appeler System.gc(), ou y a-t-il des moments où cela est acceptable? Si oui, quels sont ces moments?
301
zneak

La raison pour laquelle tout le monde dit toujours d'éviter System.gc() est qu'il s'agit d'un très bon indicateur d'un code fondamentalement cassé}. Tout code qui en dépend pour la correction est certainement cassé; ceux qui en dépendent pour la performance sont probablement cassés.

Vous ne savez pas quelle sorte de ramasse-miettes vous utilisez. Il y en a certainement qui ne "stoppent pas le monde"} comme vous l'affirmez, mais certaines machines virtuelles ne sont pas aussi intelligentes ou pour diverses raisons (peut-être sont-elles au téléphone?) Ne le font pas. Vous ne savez pas ce que ça va faire.

En outre, il n'est pas garanti de faire quoi que ce soit. La machine virtuelle Java peut simplement ignorer entièrement votre demande.

La combinaison de "vous ne savez pas ce que cela va faire", "vous ne savez pas si cela va même aider", et "vous ne devriez pas avoir besoin de l'appeler de toute façon" sont la raison pour laquelle les gens sont si énergiques en disant 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 utiliser cela, vous ne devriez pas"


EDIT pour répondre à quelques préoccupations de l'autre thread:

Après avoir lu le fil de discussion que vous avez lié, il y a quelques autres points que je voudrais souligner ... Tout d'abord, quelqu'un a suggéré que l'appel de gc() pourrait restituer de la mémoire au système. Ce n'est certainement pas nécessairement vrai - le tas Java lui-même croît indépendamment des allocations Java. 

Au fur et à mesure, la JVM conservera de la mémoire (plusieurs dizaines de mégaoctets) et étendra le tas si nécessaire. Il ne restitue pas nécessairement cette mémoire au système même lorsque vous libérez des objets Java. il est parfaitement libre de conserver la mémoire allouée à utiliser pour les allocations futures de Java.

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

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

et en particulier qu'il existe une option -XX: DisableExplicitGC VM.

231
Steven Schlansker

Il a déjà été expliqué que l'appel de system.gc()peut ne faire rien, et que tout code "ayant besoin" du garbage collector à exécuter est cassé.

Cependant, la raison pragmatique selon laquelle il est déconseillé d’appeler System.gc() est qu’elle est inefficace. Et dans le pire des cas, c'est horriblement inefficace ! Laisse-moi expliquer.

Un algorithme GC typique identifie les déchets en traversant tous les objets non-déchets du tas et en déduisant que tout objet non visité doit être vide. À partir de là, nous pouvons modéliser le travail total d’un ramasse-miettes composé d’une partie proportionnelle à la quantité de données actives et d’une autre partie proportionnelle à la quantité de déchets; c'est-à-dire work = (live * W1 + garbage * W2)

Supposons maintenant que vous procédiez comme suit dans une application à un seul thread.

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

Nous prévoyons que le premier appel fonctionnera avec (live * W1 + garbage * W2) et éliminera les déchets en suspens.

Le deuxième appel ne fera que (live* W1 + 0 * W2) et ne récupérera rien. En d’autres termes, nous avons effectué (live * W1) travail et n’avons 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 le GC aussi efficace que possible, nous devons maximiser la valeur de garbage lorsque nous exécutons le GC; c'est-à-dire attendre que le tas soit plein. (Et aussi, faites le tas aussi grand que possible. Mais c'est un sujet séparé.)

Si l'application n'interfère pas (en appelant System.gc()), le GC attendra que le segment de mémoire soit plein avant de s'exécuter, ce qui permettra une collecte efficace des déchets.1. Mais si l'application force le GC à s'exécuter, il y a des chances que le tas ne soit pas plein et le résultat est que les ordures sont collectées de manière inefficace. Et plus l'application force le GC, plus le GC devient inefficace.

Remarque: l'explication ci-dessus dissimule le fait qu'un GC moderne typique partitionne le segment de mémoire en "espaces". Le GC peut agrandir le segment de manière dynamique. Le jeu d'objets de non-garbage de l'application peut varier, etc. Même dans ce cas, le même principe de base s’applique à tous les véritables éboueurs.2. Il est inefficace de forcer le GC à fonctionner.


1 - C’est ainsi que fonctionne le collecteur "débit". Les collecteurs simultanés tels que CMS et G1 utilisent différents critères pour décider quand démarrer le ramasse-miettes.

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

139
Stephen C

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

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

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

Le conseil de ne pas compter sur lui pour faire quoi que ce soit est correct. Ne comptez pas sur son efficacité, mais donnez l’allusion que c’est maintenant un temps acceptable pour collecter. Je préfère perdre du temps dans le code où cela n'a pas d'importance (écran de chargement) que lorsque l'utilisateur interagit activement avec le programme (comme lors d'un niveau de jeu).

Il y a un moment où je vais force collection: lorsque vous essayez de découvrir est un objet particulier qui fuit (code natif ou interaction de rappel complexe jette un coup d’œil à Matlab.) Cela ne devrait jamais être utilisé dans un code de production.

42
KitsuneYMG

Les gens font un bon travail en expliquant pourquoi NE PAS utiliser, je vais donc vous raconter quelques situations dans lesquelles vous devriez l'utiliser:

(Les commentaires suivants s'appliquent à Hotspot s'exécutant sous Linux avec le collecteur CMS, où je suis confiant de dire que System.gc() invoque en fait toujours un garbage collection complet).

  1. Après le travail initial de démarrage de votre application, l’utilisation de la mémoire risque d’être catastrophique. La moitié de votre génération permanente pourrait être pleine de déchets, ce qui signifie que vous êtes beaucoup plus proche de votre premier CMS. Dans les applications où cela compte, ce n'est pas une mauvaise idée d'appeler System.gc () pour "réinitialiser" votre segment de mémoire à l'état de démarrage des données actives.

  2. Dans le même ordre d'idées que le n ° 1, si vous surveillez de près votre utilisation du tas, vous souhaitez avoir une lecture précise de votre utilisation de la mémoire de base. Si les 2 premières minutes de disponibilité de votre application sont entièrement à l'initialisation, vos données seront perturbées à moins que vous ne forçiez (ahem ... "suggérez") l'intégralité du gc. 

  3. Vous avez peut-être une application conçue pour ne jamais promouvoir quoi que ce soit à la génération permanente en cours d’exécution. Mais peut-être avez-vous besoin d'initialiser au préalable certaines données qui ne sont pas si énormes que d'être automatiquement transférées vers la génération permanente. Sauf si vous appelez System.gc () une fois que tout est configuré, vos données peuvent rester dans la nouvelle génération jusqu'à ce que le moment de leur promotion. Tout à coup, votre application super-désespérée à faible temps de latence et à faible GC est frappée d'une pénalité de SUD énorme (relativement parlant bien sûr) pour la promotion de ces objets au cours d'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 de données en direct à l'heure X doit exister dans un certain rapport par rapport à l'ensemble de données en direct à l'heure Y, il peut être utile d'appeler System.gc () une heure X et une heure Y et de comparer l'utilisation de la mémoire. .

28
JT.

L'efficacité du 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 créés il n'y a pas longtemps. Une autre est que de nombreux objets ont une durée de vie très courte (certains objets seront utilisés pendant une longue période, mais beaucoup seront ignorés quelques microsecondes après leur création).

Appeler System.gc() revient à 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 faire 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". Il peut améliorer les performances, mais la plupart du temps, il ne fait que dégrade les performances.

Pour utiliser System.gc() de manière fiable (*), vous devez connaître le fonctionnement du GC dans tous ses détails. Ces détails ont tendance à changer un peu si vous utilisez une machine virtuelle Java d'un autre fournisseur, ou la version suivante du même fournisseur, ou de la même machine virtuelle mais avec des options de ligne de commande légèrement différentes. C'est donc rarement une bonne idée, sauf si vous souhaitez aborder 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 porte rarement ses fruits.

(*) Je parle d'efficacité ici. System.gc() ne sera jamais pause un programme Java correct. Cela ne créera pas non plus une mémoire supplémentaire que la machine virtuelle Java n'aurait pas pu obtenir autrement: avant de lancer une variable OutOfMemoryError, celle-ci effectue le travail de System.gc(), même en dernier recours.

9
Thomas Pornin

Parfois (pas souvent!) Vous en savez vraiment plus sur l'utilisation de la mémoire passée, actuelle et future, contrairement à l'exécution. Cela n'arrive pas très souvent, et je prétends ne jamais dans une application Web pendant que des pages normales sont servies.

Il y a plusieurs années, je travaille sur un générateur de rapports, qui

  • Eu un seul fil 
  • Lire la "demande de rapport" d'une file d'attente
  • Chargé les données nécessaires pour le rapport de la base de données
  • Généré le rapport et envoyé par courrier électronique.
  • Répété pour toujours, dormir quand il n'y avait pas de demandes en attente.
  • Il n'a pas réutilisé les données entre les rapports et n'a effectué aucun encaissement.

Tout d'abord, comme il ne s'agissait pas de temps réel et que les utilisateurs s'attendaient à recevoir un rapport, un délai d'attente pour l'exécution du GC n'était pas un problème, mais nous devions produire des rapports plus rapidement que prévu. 

En regardant les grandes lignes du processus, il est clair que.

  • Nous savons qu'il y aurait très peu d'objets vivants juste après l'envoi d'un rapport par courrier électronique, car la demande suivante n'avait pas encore commencé à être traitée.
  • Il est bien connu que le coût d’exécution d’un cycle de récupération de place dépend du nombre d’objets actifs) la quantité de place n’a que peu d’effet sur le coût d’une analyse de CPG.
  • Que lorsque la file est vide, il n'y a rien de mieux à faire, puis exécutez le CPG.

Par conséquent, il était clairement intéressant de procéder à une exécution du catalogue global chaque fois que la file d'attente des demandes était vide; il n'y avait aucun inconvénient à cela.

Il peut être intéressant de procéder à une analyse du GC une fois chaque rapport envoyé par courrier électronique, car nous savons que le moment est propice pour une analyse du GC. Toutefois, si l'ordinateur disposait de suffisamment de RAM, de meilleurs résultats seraient obtenus en retardant l'exécution du CPG.

Ce comportement a été configuré sur une base par installation, pour certains clients activant un CPG forcé après chaque rapport grandement accéléré pour améliorer la protection des rapports. (Je suppose que cela était dû à une mémoire insuffisante sur leur serveur et au bon fonctionnement de nombreux autres processus, d'où une pagination réduite forcée pour le GC.)

Nous n'avons jamais détecté qu'une installation qui n'en bénéficiait pas était une exécution forcée du CPG chaque fois que la file d'attente de travail était vide.

Mais, soyons clairs, ce qui précède n’est pas un cas courant.

6
Ian Ringrose

C’est une question très gênante, et j’ai le sentiment que beaucoup s’opposent à Java en dépit de l’utilité d’un langage.

Le fait que vous ne puissiez pas faire confiance à «System.gc» pour faire quoi que ce soit est incroyablement décourageant et peut facilement invoquer le sentiment «peur, incertitude, doute» du langage.

Dans de nombreux cas, il est agréable de gérer les pics de mémoire que vous avez provoqués avant qu'un événement important se produise, ce qui inciterait les utilisateurs à penser que votre programme est mal conçu/ne répond pas.

Avoir la capacité de contrôler le ramassage des ordures serait un très bon outil d’éducation, car il aiderait mieux les gens à comprendre le fonctionnement du ramassage des ordures et à faire en sorte que les programmes exploitent son comportement par défaut ainsi que son comportement contrôlé.

Permettez-moi de passer en revue les arguments de ce fil.

  1. C'est inefficace:

Souvent, le programme ne fait rien et vous savez qu'il ne fait rien du fait de la façon dont il a été conçu. Par exemple, il peut s’agir d’une longue attente avec une boîte de message d’attente volumineuse et, à la fin, il peut tout aussi bien ajouter un appel à la collecte des déchets, car le temps de son exécution ne prend que très peu de temps. longue attente, mais évitera que gc agisse au beau milieu d’une opération plus importante.

  1. C'est toujours une mauvaise pratique et indique un code cassé.

Je ne suis pas d'accord, peu importe ce que vous avez comme éboueur. Son travail consiste à suivre les déchets et à les nettoyer. 

En appelant le gc pendant les périodes où l'utilisation est moins critique, vous réduisez ses chances de s'exécuter lorsque votre vie dépend du code spécifique en cours d'exécution, mais décide à la place de collecter des déchets. 

Bien sûr, il peut ne pas se comporter comme vous le souhaitez ou si vous l'attendez, mais lorsque vous souhaitez l'appeler, vous savez que rien ne se passe et que l'utilisateur est prêt à tolérer les lenteurs/les temps morts. Si le System.gc fonctionne, tant mieux! Si ce n'est pas le cas, au moins vous avez essayé. Il n’ya tout simplement aucun inconvénient, à moins que le ramasse-miettes ait des effets secondaires inhérents qui font quelque chose de terriblement inattendu à la façon dont un ramasse-poubelles est supposé se comporter s’il est appelé manuellement, ce qui en soi suscite la méfiance.

  1. Ce n'est pas un cas d'utilisation courant:

C'est un cas d'utilisation qui ne peut pas être réalisé de manière fiable, mais qui pourrait l'être si le système était conçu de cette manière. C'est comme faire un feu de signalisation et faire en sorte que certains/tous les boutons des feux de circulation ne fassent rien, cela vous amène à vous demander pourquoi le bouton est là pour commencer, javascript n'a pas la fonction de ne le scrute pas autant pour ça.

  1. La spécification indique que System.gc () est un indice que GC doit s'exécuter et que VM est libre de l'ignorer.

qu'est-ce qu'un "indice"? Qu'est-ce que "ignorer"? un ordinateur ne peut pas simplement prendre des allusions ou ignorer quelque chose, il existe des chemins de comportement stricts qui peuvent être dynamiques et qui sont guidés par l’intention du système. Une bonne réponse inclurait ce que le ramasse-miettes est en train de faire, au niveau de la mise en œuvre, ce qui l’empêche de collecter lorsque vous le demandez. La fonctionnalité est-elle simplement un nop? Y a-t-il une sorte de conditions que je dois remplir? Quelles sont ces conditions?

En l'état actuel des choses, le GC de Java ressemble souvent à un monstre auquel vous ne faites pas confiance. Vous ne savez pas quand cela va ou vient, vous ne savez pas ce que ça va faire, comment ça va le faire. J'imagine que certains experts ont une meilleure idée du fonctionnement de la collecte des ordures sur une base donnée, mais une grande majorité espère simplement qu'elle "fonctionne tout simplement". Il est frustrant de devoir faire confiance à un algorithme à l'apparence opaque.

Il y a un grand fossé entre lire quelque chose ou apprendre quelque chose et en voir la mise en œuvre, les différences entre les systèmes, et pouvoir jouer avec sans avoir à regarder le code source. Cela crée de la confiance et un sentiment de maîtrise/compréhension/contrôle.

Pour résumer, il existe un problème inhérent aux réponses "cette fonctionnalité peut ne rien faire, et je n’entrerai pas dans les détails pour savoir quand elle fait quelque chose et quand elle ne le fait pas et pourquoi elle ne le fera pas ou ne le fera pas, impliquant souvent qu’il est tout simplement contraire à la philosophie d’essayer de le faire, même si l’intention qui la sous-tend est raisonnable ".

Il est peut-être acceptable que Java GC se comporte comme il le fait ou pas, mais pour le comprendre, il est difficile de savoir dans quelle direction aller pour obtenir un aperçu complet de ce que vous pouvez faire confiance au GC pour le faire. il est trop facile de se méfier de la langue, car le but d’une langue est d’avoir un comportement contrôlé jusqu’à un degré philosophique (il est facile pour un programmeur, en particulier les novices, de tomber dans une crise existentielle à partir de certains comportements système/langage). sont capables de tolérer (et si vous ne le pouvez pas, vous n'utiliserez tout simplement pas la langue tant que vous n'y serez pas obligé), et plus de choses que vous ne pouvez pas contrôler sans raison connue, pourquoi vous ne pouvez pas les contrôler est intrinsèquement nocif.

6
Dmitry

J'écris peut-être du code de merde, mais je me suis rendu compte que cliquer sur l'icône de la corbeille sur les IDE Eclipse et Netbeans est une "bonne pratique".

2
Ryan Fernandes

Premièrement, il y a une différence entre le spec et la réalité. La spécification indique que System.gc () est un indice que GC doit s'exécuter et que VM est libre de l'ignorer. En réalité, la VM never ignorera un appel à System.gc ().

L'appel de GC entraîne une surcharge non négligeable de l'appel et si vous le faites à un moment donné au hasard, il est probable que vos efforts ne seront pas récompensés. Par ailleurs, une collecte déclenchée naturellement est très susceptible de récupérer les coûts de l'appel. Si vous avez des informations indiquant qu'un GC doit être exécuté, vous pouvez appeler System.gc () et vous devriez en voir les avantages. Cependant, selon mon expérience, cela ne se produit que dans quelques cas Edge, car il est très peu probable que vous disposiez d'informations suffisantes pour comprendre si et quand System.gc () devrait être appelé.

Un exemple répertorié ici, frapper la poubelle dans votre IDE. Si vous partez pour une réunion, pourquoi ne pas la frapper. Les frais généraux ne vous affecteront pas et le tas pourrait être nettoyé pour votre retour. Faites-le dans un système de production et les appels fréquents à collecter le feront ternir! Même des appels occasionnels tels que ceux effectués par RMI peuvent perturber les performances.

2
Kirk

Oui, appeler System.gc () ne garantit pas son exécution, il s'agit d'une requête de la machine virtuelle qui peut être ignorée. De la docs:

L'appel de la méthode gc suggère que la machine virtuelle Java consacre des efforts considérables au recyclage des objets inutilisés.

C'est presque toujours une mauvaise idée de l'appeler, car la gestion automatique de la mémoire sait généralement mieux que vous quand le faire. Il le fera lorsque son pool interne de mémoire disponible est faible ou que le système d'exploitation demande à ce que de la mémoire soit rendue. 

Il peut être acceptable d’appeler System.gc () si vous savez que cela aide. Je veux dire par là que vous avez soigneusement testé et mesuré le comportement des deux scénarios sur la plate-forme de déploiement, et vous pouvez montrer que cela aide. Sachez cependant que le gc n'est pas facilement prévisible - cela peut aider lors d'une course et faire mal à une autre.

1
tom
  1. Comme les objets sont alloués dynamiquement à l’aide du nouvel opérateur,
    Vous vous demandez peut-être comment de tels objets sont détruits et leur
    mémoire libérée pour une réallocation ultérieure.

  2. Dans certains langages, tels que C++, les objets alloués dynamiquement doiventbe être libérés manuellement à l'aide d'un opérateur de suppression.

  3. Java adopte une approche différente. il gère la désaffectation pour vous automatiquement.
  4. La technique permettant de réaliser cela s'appelle garbage collection . Elle fonctionne comme suit: lorsqu'il n'existe aucune référence à un objet, cet objet est supposé ne plus être nécessaire et la mémoire occupée par l'objet peut être récupérée. Il n’existe aucun besoin explicite de détruire des objets comme en C++.
  5. Le ramassage des ordures ne se produit que sporadiquement (voire pas du tout) pendant l'exécution du programme
  6. Cela ne se produira pas simplement parce qu'il existe un ou plusieurs objets déjà utilisés.
  7. En outre, différentes implémentations d'exécution Java adopteront différentes approches de la récupération de place, mais, dans la plupart des cas, vous ne devriez pas avoir à y penser pendant l'écriture de vos programmes.
0
Tanmay Delhikar

D'après mon expérience, utiliser System.gc () est en réalité une forme d'optimisation spécifique à la plate-forme (où "plate-forme" est la combinaison de l'architecture matérielle, du système d'exploitation, de la version de la machine virtuelle Java et de plusieurs autres paramètres d'exécution tels que RAM disponible), parce que son comportement, bien que relativement prévisible sur une plate-forme spécifique, peut (et variera) considérablement d’une plate-forme à l’autre.

Oui, il y a des situations dans lesquelles System.gc () améliorera les performances (perçues). Par exemple, les retards sont tolérables dans certaines parties de votre application, mais pas dans d’autres (l’exemple de jeu cité ci-dessus, dans lequel vous voulez que la GC se produise au début d’un niveau, pas pendant le niveau).

Cependant, si cela va aider ou faire mal (ou ne rien faire) dépend fortement de la plate-forme (telle que définie ci-dessus).

Je pense donc qu’elle est valide en tant qu’optimisation spécifique de la plate-forme de dernier recours (c’est-à-dire si d’autres optimisations de performances ne suffisent pas). Mais vous ne devriez jamais l'appeler simplement parce que vous pensez que cela pourrait aider (sans critères de référence spécifiques), car il est fort probable que cela ne le sera pas.

0
sleske