Pourquoi les objets Java ne sont-ils pas supprimés immédiatement après qu'ils ne sont plus référencés?

77

En Java, dès qu’un objet n’a plus de références, il est éligible à la suppression, mais la JVM décide à quel moment l’objet est réellement supprimé. Pour utiliser la terminologie Objective-C, toutes les références Java sont intrinsèquement "fortes". Cependant, dans Objective-C, si un objet ne possède plus aucune référence forte, il est immédiatement supprimé. Pourquoi n'est-ce pas le cas en Java?

moonman239
la source
46
Vous ne devez pas vous soucier du moment où les objets Java sont réellement supprimés. C'est un détail de mise en œuvre.
Basile Starynkevitch
154
@BasileStarynkevitch Vous devez absolument vous préoccuper du fonctionnement de votre système / plate-forme. Poser les questions «comment» et «pourquoi» est l’un des meilleurs moyens de devenir un meilleur programmeur (et, plus généralement, une personne plus intelligente).
Artur Biesiadowski
6
Que fait l’objectif C quand il y a des références circulaires? Je suppose que ça leur fuit?
Mehrdad
45
@ArturBiesiadowksi: Non, la spécification Java ne dit pas quand un objet est supprimé (et de même pour R5RS ). Vous pourriez et devriez probablement développer votre programme Java au cas où cette suppression ne se produirait jamais (et pour les processus de courte durée, comme un monde Java hello, cela ne se produit effectivement pas). Vous pouvez vous soucier de l'ensemble des objets vivants (ou de la consommation de mémoire), ce qui est une histoire différente.
Basile Starynkevitch
28
Un jour, le novice a dit au maître "J'ai une solution à notre problème d'allocation. Nous attribuerons un compte de référence à chaque allocation, et lorsqu'il atteindra zéro, nous pourrons supprimer l'objet". Le maître a répondu "Un jour le novice a dit au maître" J'ai une solution ...
Eric Lippert

Réponses:

79

Tout d’abord, Java a des références faibles et une autre catégorie de meilleur effort appelée références logicielles. Les références faibles par rapport aux références fortes sont un problème totalement distinct du comptage des références par rapport au nettoyage des ordures.

Deuxièmement, il existe des modèles d'utilisation de la mémoire qui peuvent rendre la collecte des ordures plus efficace dans le temps en sacrifiant de l'espace. Par exemple, les objets plus récents sont beaucoup plus susceptibles d'être supprimés que les objets plus anciens. Donc, si vous attendez un peu entre les balayages, vous pouvez supprimer la plupart de la nouvelle génération de mémoire, tout en déplaçant les quelques survivants vers un stockage à plus long terme. Ce stockage à long terme peut être analysé beaucoup moins fréquemment. La suppression immédiate via la gestion manuelle de la mémoire ou le comptage de références est beaucoup plus susceptible de se fragmenter.

C'est un peu la différence entre faire les courses une fois par salaire et aller tous les jours chercher juste assez de nourriture pour une journée. Votre grand voyage prendra beaucoup plus de temps qu'un petit voyage individuel, mais globalement, vous gagnerez du temps et probablement de l'argent.

Karl Bielefeldt
la source
58
La femme d'un programmeur l'envoie au supermarché. Elle lui dit: "Achetez une miche de pain et si vous voyez des œufs, prenez-en une douzaine." Le programmeur revient plus tard avec une douzaine de pains sous le bras.
Neil
7
Je suggère de mentionner que le temps gc de nouvelle génération est généralement proportionnel à la quantité d' objets vivants . Par conséquent, si vous supprimez plus d'objets supprimés, leur coût ne sera pas payé du tout dans de nombreux cas. Supprimer est aussi simple que de faire basculer le pointeur de l'espace survivant et de mettre éventuellement à zéro tout l'espace mémoire dans un grand memset (vous ne savez pas s'il est effectué à la fin de la procédure ou amorti lors de l'attribution des tlabs ou des objets eux-mêmes dans les jvms actuels)
Artur Biesiadowski
64
@ Neil ne devrait-il pas s'agir de 13 pains?
JAD
67
"Off par une erreur sur le couloir 7"
joeytwiddle
13
@JAD J'aurais dit 13 ans, mais la plupart n'ont pas tendance à l'obtenir. ;)
Neil
86

Parce que bien savoir que quelque chose n'est plus référencé n'est pas facile. Même pas près de facile.

Et si vous avez deux objets qui se référent? Restent-ils pour toujours? Si vous élargissez cette ligne de pensée à la résolution de toute structure de données arbitraire, vous comprendrez vite pourquoi la JVM ou d'autres éboueurs sont obligés d'employer des méthodes beaucoup plus sophistiquées pour déterminer ce qui reste nécessaire et ce qui peut rester.

comment s'appelle-t-il
la source
7
Vous pouvez également adopter une approche Python dans laquelle vous utilisez autant que possible le recours à une nouvelle comptabilité, en ayant recours à un GC lorsque vous vous attendez à ce qu'il y ait des dépendances circulaires qui perdent de la mémoire. Je ne vois pas pourquoi ils n'auraient pas pu compter en plus de GC?
Mehrdad
27
@ Mehrdad Ils pourraient. Mais ce serait probablement plus lent. Rien ne vous empêche de mettre cela en œuvre, mais ne vous attendez pas à battre l'un des GC dans Hotspot ou OpenJ9.
Josef
21
@ jpmc26 car si vous supprimez des objets dès qu'ils ne sont plus utilisés, la probabilité est élevée que vous les supprimiez dans des situations de charge élevée, ce qui augmente encore la charge. Le CPG peut fonctionner lorsqu'il y a moins de charge. Le décompte de références lui-même est une petite surcharge pour chaque référence. De plus, avec un CPG, vous pouvez souvent éliminer une grande partie de la mémoire sans référence sans gérer les objets individuels.
Josef
33
@Josef: compter correctement les références n'est pas gratuit non plus; La mise à jour du nombre de références nécessite des incréments / décréments atomiques, ce qui est étonnamment coûteux , en particulier pour les architectures multicœurs modernes. Dans CPython, le problème ne se pose pas vraiment (CPython est extrêmement lent en lui-même et la GIL limite ses performances multithread à des niveaux monocœur), mais dans un langage plus rapide qui prend également en charge le parallélisme, cela peut poser problème. Ce n'est pas une chance pour que PyPy se débarrasse complètement du comptage de références et utilise simplement GC.
Matteo Italia
10
@Mehrdad une fois que vous avez implémenté votre comptage de références GC pour Java, je le testerai volontiers pour trouver le cas où ses performances seraient inférieures à celles de toute autre implémentation de GC.
Josef
45

Autant que je sache, la spécification de la machine virtuelle Java (écrite en anglais) ne mentionne pas le moment précis un objet (ou une valeur) doit être supprimé, et laisse cela à la mise en œuvre (de même pour R5RS ). Cela nécessite ou suggère un ramasse-miettes, mais laisse les détails à la mise en œuvre. Et de même pour la spécification Java.

N'oubliez pas que les langages de programmation sont des spécifications (de syntaxe , de sémantique , etc.) et non des implémentations logicielles. Un langage comme Java (ou sa machine virtuelle Java) a de nombreuses implémentations. Sa spécification est publiée , téléchargeable (afin que vous puissiez l'étudier) et écrite en anglais. §2.5.3 Le tas de la spécification de la JVM mentionne un ramasse-miettes:

Le stockage de tas pour les objets est récupéré par un système de gestion de stockage automatique (appelé garbage collector); les objets ne sont jamais explicitement désalloués. La machine virtuelle Java n'assume aucun type particulier de système de gestion de stockage automatique

(L’accent est à moi; la finalisation de BTW est mentionnée au § 12.6 de la spécification Java, et un modèle de mémoire est au § 17.4 de la spécification Java)

Donc (en Java) vous ne devriez pas prendre soin quand un objet est supprimé , et vous pouvez coder comme, si cela ne se produit pas (par le raisonnement dans une abstraction où vous ignorez que). Bien sûr, vous devez vous préoccuper de la consommation de mémoire et des objets vivants, ce qui est une question différente . Dans plusieurs cas simples (pensez à un programme "hello world"), vous êtes en mesure de prouver - ou de vous convaincre - que la mémoire allouée est plutôt petite (par exemple, moins d'un gigaoctet), et vous ne vous souciez plus du tout. suppression d' objets individuels . Dans plus de cas, vous pouvez vous convaincre que les objets vivants(ou ceux qui sont joignables, ce qui est plus facile à raisonner que les vivants) ne dépasse jamais une limite raisonnable (et vous vous fiez alors à GC, mais vous vous moquez de savoir quand et comment la collecte des ordures a lieu). Lisez à propos de la complexité de l'espace .

J'imagine que sur plusieurs implémentations de machine virtuelle Java exécutant un programme Java de courte durée, comme celui de hello world, le ramasse-miettes n'est pas déclenché du tout et aucune suppression ne se produit. AFAIU, un tel comportement est conforme aux nombreuses spécifications Java.

La plupart des implémentations JVM utilisent des techniques de copie générationnelles (du moins pour la plupart des objets Java, ceux qui n'utilisent pas la finalisation ou des références faibles ; la finalisation n'est pas garantie et peut être différée. Il s'agit donc d'une fonctionnalité utile que votre code ne devrait pas utiliser. dépend beaucoup de) dans lequel la notion de suppression d’un objet individuel n’a aucun sens (puisqu'un large bloc de mémoire contenant des zones de mémoire pour de nombreux objets, peut-être plusieurs mégaoctets à la fois, est libéré à la fois).

Si la spécification JVM exigeait que chaque objet soit supprimé le plus tôt possible (ou imposait simplement plus de contraintes à la suppression d'objet), les techniques de GC générationnelles efficaces seraient interdites et les concepteurs de Java et de la JVM ont été avisés de l'éviter.

En passant, il est possible qu’une JVM naïve qui ne supprime jamais d’objets et ne libère pas de mémoire se conforme aux spécifications (la lettre, pas l’esprit) et puisse certainement exécuter un problème de bonjour dans la pratique (notez que la plupart les programmes Java minuscules et de courte durée n'allouent probablement pas plus de quelques giga-octets de mémoire). Bien sûr, une telle machine virtuelle ne mérite pas d’être mentionnée et n’est qu’un jouet (comme cette implémentation de mallocC). Voir le GC Epsilon NoOp pour plus d'informations. Les machines virtuelles Java réelles sont des logiciels très complexes et combinent plusieurs techniques de récupération de place.

En outre, Java n'est pas identique à la machine virtuelle Java et vous avez des implémentations Java qui s'exécutent sans machine virtuelle (par exemple , des compilateurs Java en avance sur le temps , le moteur d'exécution Android ). Dans certains cas (principalement académiques), vous pouvez imaginer (techniques dites de "récupération de place au moment de la compilation") qu’un programme Java n’alloue ni ne supprime au moment de l’exécution (par exemple parce que le compilateur optimiseur a été assez intelligent pour utiliser uniquement pile d'appels et variables automatiques ).

Pourquoi les objets Java ne sont-ils pas supprimés immédiatement après qu'ils ne sont plus référencés?

Parce que les spécifications Java et JVM ne l'exigent pas.


Lisez le manuel du GC pour plus d’informations (et les spécifications de la JVM ). Notez que le fait d'être en vie (ou utile pour les calculs futurs) d'un objet est une propriété de programme entier (non modulaire).

Objective-C privilégie une approche de comptage de références pour la gestion de la mémoire . Et cela a aussi des pièges (par exemple , l'Objective-C programmeur doit se soucier de références circulaires par explicitant références faibles, mais une machine virtuelle Java traite les références circulaires bien dans la pratique sans nécessiter l' attention du programmeur Java).

Il n'y a pas de solution miracle dans la programmation et la conception de langages de programmation (soyez conscient du problème de l' arrêt, car être un objet vivant utile est indécidable en général).

Vous pouvez également lire SICP , Pragmatique sur les langages de programmation , Le Livre du Dragon , Lisp In Small Pieces et les systèmes d’exploitation: Trois pièces faciles . Ils ne concernent pas Java, mais ils vous ouvriront l'esprit et devraient vous aider à comprendre ce que doit faire une machine virtuelle et comment elle pourrait fonctionner (avec d'autres éléments) sur votre ordinateur. Vous pouvez également passer plusieurs mois (ou plusieurs années) dans l' étude du code source complexe existant open source implémentations de la JVM (comme OpenJDK , qui a plusieurs millions de lignes de code source).

Basile Starynkevitch
la source
20
"il est possible qu'une machine virtuelle naïve qui ne supprime jamais les objets et ne libère pas de mémoire soit conforme aux spécifications". Elle est certainement conforme aux spécifications! En fait, Java 11 ajoute un ramasse-miettes sans opérations pour, entre autres, les programmes de très courte durée.
Michael
6
"vous ne devez pas vous soucier de la suppression d'un objet" En désaccord. Tout d'abord, vous devez savoir que RAII n'est plus un modèle réalisable, et que vous ne pouvez pas compter sur finalizeaucune gestion de ressources (de descripteurs de fichiers, de connexions à une base de données, de ressources gpu, etc.).
Alexandre
4
@Michael Cela convient parfaitement pour le traitement par lots avec un plafond de mémoire utilisé. Le système d'exploitation peut simplement dire "toute la mémoire utilisée par ce programme a maintenant disparu!" après tout, ce qui est plutôt rapide. En effet, de nombreux programmes en C ont été écrits de cette façon, en particulier dans le premier monde Unix. Pascal avait le très horrible "réinitialiser le pointeur pile / tas sur un point de contrôle pré-enregistré" qui vous permettait de faire à peu près la même chose, bien que ce fût très dangereux - marquez, démarrez la sous-tâche, réinitialisez.
Luaan
6
@Alexander en général en dehors de C ++ (et quelques langages qui en dérivent intentionnellement), en supposant que RAII fonctionnera uniquement avec les finaliseurs est un anti-modèle, contre lequel il convient de se mettre en garde et de le remplacer par un bloc de contrôle de ressources explicite. Tout l'intérêt de GC est que la durée de vie et les ressources sont découplées, après tout.
Leushenko
3
@ Leushenko Je suis tout à fait en désaccord avec le fait que "la durée de vie et les ressources sont découplées" est le "point entier" de la GC. C’est le prix négatif que vous payez pour l’essentiel du GC: une gestion de la mémoire simple et sûre. "supposer que RAII fonctionnera uniquement avec les finaliseurs est un anti-motif" En Java? Peut-être. Mais pas dans CPython, Rust, Swift ou Objective C. "mis en garde contre et remplacé par un bloc de contrôle de ressources explicite" "Non, ils sont strictement plus limités. Un objet qui gère une ressource via RAII vous fournit un identifiant pour transmettre la vie délimitée. Un bloc try-with-resource est limité à une seule portée.
Alexander
23

Pour utiliser la terminologie Objective-C, toutes les références Java sont intrinsèquement "fortes".

Ce n'est pas correct - Java contient à la fois des références faibles et souples, bien que celles-ci soient implémentées au niveau de l'objet plutôt que comme mots-clés du langage.

En Objective-C, si un objet n'a plus aucune référence forte, il est immédiatement supprimé.

Cela n’est pas non plus nécessairement correct. Certaines versions de l’objectif C utilisaient en effet un garbage collector générationnel. Les autres versions ne comportaient aucune récupération de place.

Il est vrai que les versions les plus récentes d'Objective C utilisent un comptage de référence automatique (ARC) plutôt qu'un GC basé sur une trace, ce qui entraîne (souvent) la "suppression" de l'objet lorsque ce nombre de références atteint zéro. Cependant, notez qu'une implémentation de machine virtuelle Java peut également être conforme et fonctionner exactement de cette façon (enfin, elle pourrait être conforme et ne pas avoir de GC.)

Alors, pourquoi la plupart des implémentations JVM ne font-elles pas cela et utilisent-elles des algorithmes de GC basés sur la trace?

En termes simples, ARC n’est pas aussi utopique qu’il semble au premier abord:

  • Vous devez incrémenter ou décrémenter un compteur chaque fois qu'une référence est copiée, modifiée ou sort du cadre, ce qui entraîne un surcoût de performance évident.
  • ARC ne peut pas facilement effacer les références cycliques, car elles ont toutes une référence les unes aux autres. Par conséquent, leur nombre de références n'atteint jamais zéro.

Bien entendu, ARC présente des avantages: sa mise en œuvre est simple et sa collecte est déterministe. Mais les inconvénients ci-dessus, entre autres, expliquent la raison pour laquelle la plupart des implémentations de machine virtuelle Java utilisent un GC générationnel basé sur les traces.

berry120
la source
1
Ce qui est amusant, c’est que Apple a opté pour ARC, précisément parce qu’ils ont constaté qu’en pratique, il surpasse largement les autres GC (en particulier ceux générationnels). Pour être juste, cela est surtout vrai sur les plates-formes à mémoire limitée (iPhone). Mais je m'opposerais à votre affirmation selon laquelle «l'ARC n'est pas aussi utopique qu'il y paraît», en affirmant que les GC générationnels (et d'autres déterministes) ne sont pas aussi utopiques qu'ils le paraissent en premier: la destruction déterministe est probablement une meilleure option dans le futur. grande majorité des scénarios.
Konrad Rudolph
3
@ KonradRudolph, même si je suis plutôt un partisan de la destruction déterministe, je ne pense pas que «la meilleure option dans la grande majorité des scénarios» ne tient pas. C'est certainement une meilleure option lorsque la latence ou la mémoire est plus importante que le débit moyen, et en particulier lorsque la logique est raisonnablement simple. Mais ce n’est pas comme si il n’y avait pas beaucoup d’applications complexes nécessitant beaucoup de références cycliques, etc., mais nécessitant un fonctionnement moyen rapide, mais se soucient peu de la latence et ont beaucoup de mémoire disponible. Pour ceux-ci, il est douteux que l'ARC soit une bonne idée.
gauche vers
1
@leftaroundabout Dans «la plupart des scénarios», ni le débit ni la pression de la mémoire ne constituent un goulot d'étranglement. Par conséquent, cela n'a pas d'importance. Votre exemple est un scénario spécifique. Certes, ce n'est pas très rare, mais je n'irais pas aussi loin que d'affirmer que c'est plus courant que d'autres scénarios où l'ARC est mieux adapté. De plus, ARC peut très bien gérer les cycles. Cela nécessite juste une intervention simple et manuelle du programmeur. Cela le rend moins idéal mais à peine un facteur décisif. Je soutiens que la finalisation déterministe est une caractéristique beaucoup plus importante que vous ne le prétendez.
Konrad Rudolph
3
@ KonradRudolph Si ARC requiert une intervention manuelle simple du programmeur, il ne traite pas des cycles. Si vous commencez à utiliser beaucoup de listes à double liaison, ARC se transforme en allocation de mémoire manuelle. Si vous avez de gros graphiques arbitraires, ARC vous oblige à écrire un ramasse-miettes. L'argument du GC serait que les ressources devant être détruites ne relèvent pas du sous-système mémoire et que, pour en suivre un nombre relativement réduit, elles devraient être finalisées de manière déterministe au moyen d'une simple intervention manuelle du programmeur.
prosfilaes
2
@KonradRudolph ARC et les cycles entraînent fondamentalement des fuites de mémoire s'ils ne sont pas gérés manuellement. Dans des systèmes assez complexes, des fuites majeures peuvent survenir si, par exemple, un objet stocké dans une carte stocke une référence à cette carte, modification qui pourrait être apportée par un programmeur non responsable des sections de code créant et détruisant cette carte. Les grands graphiques arbitraires ne signifient pas que les pointeurs internes ne sont pas puissants, mais que les éléments liés disparaissent sans problème. Je ne dirai pas si la gestion manuelle de fuites de mémoire pose moins de problèmes que la fermeture manuelle de fichiers, mais c'est une réalité.
prosfilaes
5

Java ne spécifie pas précisément quand l'objet est collecté car cela donne aux implémentations la liberté de choisir comment gérer le garbage collection.

Il existe de nombreux mécanismes de récupération de place, mais ceux qui garantissent la collecte immédiate d'un objet sont presque entièrement basés sur le comptage de références (je ne connais aucun algorithme qui rompt cette tendance). Le comptage de références est un outil puissant, mais il a un coût de maintien du comptage de références. Dans le code simple, cela n’est rien d’autre qu’un incrément et un décrément. Par conséquent, l’affectation d’un pointeur peut coûter trois fois plus cher en code compté de référence qu’elle ne le fait en code compté non référencé (si le compilateur peut tout casser jusqu’à la machine). code).

Dans le code multithread, le coût est plus élevé. Il appelle soit des incréments / décréments atomiques, soit des verrous, ce qui peut coûter cher. Sur un processeur moderne, une opération atomique peut être environ 20 fois plus chère qu'une simple opération de registre (varie évidemment d'un processeur à l'autre). Cela peut augmenter le coût.

Donc, avec cela, nous pouvons considérer les compromis faits par plusieurs modèles.

  • Objective-C se concentre sur ARC - comptage automatique des références. Leur approche consiste à utiliser le décompte de références pour tout. Il n'y a pas de détection de cycle (à ma connaissance), donc les programmeurs sont censés empêcher les cycles de se produire, ce qui coûte du temps de développement. Leur théorie est que les pointeurs ne sont pas souvent assignés, et leur compilateur peut identifier des situations dans lesquelles l'incrémentation / décrémentation des comptages de références ne peut pas causer la mort d'un objet et éliminer complètement ces incréments / décréments. Ainsi, ils minimisent le coût du comptage des références.

  • CPython utilise un mécanisme hybride. Ils utilisent des comptages de références, mais ils ont également un ramasse-miettes qui identifie les cycles et les libère. Cela procure les avantages des deux mondes, au détriment des deux approches. CPython doit à la fois gérer le nombre de références etfaire la comptabilité pour détecter les cycles. CPython s'en tire de deux manières. Le poing est que CPython n’est vraiment pas entièrement multithread. Il possède un verrou appelé GIL qui limite le multithreading. Cela signifie que CPython peut utiliser des incréments / décréments normaux plutôt que des incréments atomiques, ce qui est beaucoup plus rapide. CPython est également interprété, ce qui signifie que des opérations telles que l’affectation à une variable prennent déjà une poignée d’instructions plutôt que seulement 1. Le coût supplémentaire associé aux incréments / décréments, effectué rapidement dans le code C, pose moins de problèmes, car nous ' Nous avons déjà payé ce coût.

  • Java adopte l'approche consistant à ne pas garantir du tout un système de référence compté. En effet, la spécification ne dit rien sur la façon dont les objets sont gérés, à part le fait qu'il y aura un système de gestion de stockage automatique. Cependant, la spécification suggère également fortement l'hypothèse qu'il s'agira d'un ramassage des ordures de manière à gérer les cycles. En ne spécifiant pas la date d'expiration des objets, Java gagne la liberté d'utiliser des collecteurs qui ne perdent pas de temps en incrémentation / décrémentation. En effet, des algorithmes intelligents tels que les éboueurs générationnels peuvent même traiter de nombreux cas simples sans même regarder les données en cours de récupération (ils doivent seulement regarder les données qui sont toujours référencées).

Nous pouvons donc constater que chacun de ces trois pays a dû faire des compromis. Le meilleur compromis dépend largement de la nature de l'utilisation de la langue.

Cort Ammon
la source
4

Bien que finalizereliée au GC de Java, la récupération de place ne s'intéresse pas aux objets morts, mais aux objets vivants. Sur certains systèmes du GC (y compris éventuellement des implémentations de Java), la seule chose qui distingue un groupe de bits représentant un objet d'un groupe de stockage inutilisé peut être l'existence de références à l'ancien. Tandis que les objets avec les finaliseurs sont ajoutés à une liste spéciale, d'autres objets peuvent ne comporter aucun élément dans l'univers indiquant que leur stockage est associé à un objet, à l'exception des références conservées dans le code utilisateur. Lorsque la dernière référence de ce type est écrasée, le modèle de bits en mémoire cesse immédiatement d'être reconnaissable en tant qu'objet, que quelque chose dans l'univers le sache ou non.

Le ramassage des ordures n'a pas pour objet de détruire des objets pour lesquels il n'existe aucune référence, mais plutôt d'accomplir trois choses:

  1. Invalidez les références faibles qui identifient les objets auxquels aucune référence très accessible n'est associée.

  2. Effectuez une recherche dans la liste d'objets du système avec les finaliseurs pour voir si aucun de ceux-ci ne comporte de référence trop accessible.

  3. Identifiez et consolidez les zones de stockage qui ne sont utilisées par aucun objet.

Notez que l'objectif principal du GC est le n ° 3 et que plus on attend longtemps avant de le faire, plus il y a de chances que la consolidation présente des opportunités. Il est logique de faire le n ° 3 dans les cas où on aurait une utilisation immédiate pour le stockage, mais sinon, il est plus logique de le différer.

supercat
la source
5
En réalité, gc n'a qu'un seul objectif: simuler une mémoire infinie. Tout ce que vous avez désigné comme objectif est soit une imperfection de l'abstraction, soit un détail de la mise en œuvre.
Déduplicateur
@Déduplicateur: les références faibles offrent une sémantique utile qui ne peut être obtenue sans l'aide du GC.
Supercat
Bien sûr, les références faibles ont une sémantique utile. Mais cette sémantique serait-elle nécessaire si la simulation était meilleure?
Deduplicator
@Duplicator: Oui. Prenons une collection qui définit la manière dont les mises à jour interagiront avec l'énumération. Une telle collection peut avoir besoin de contenir des références faibles à des énumérateurs en direct. Dans un système à mémoire illimitée, une collection qui était itérée à plusieurs reprises verrait sa liste d’énumérateurs intéressés s’allonger sans limite. La mémoire requise pour cette liste ne poserait pas de problème, mais le temps nécessaire pour la parcourir entraînerait une dégradation des performances du système. L'ajout de GC peut faire la différence entre un algorithme O (N) et O (N ^ 2).
Supercat
2
Pourquoi voudriez-vous informer les enquêteurs au lieu d’ajouter à une liste et de les laisser se chercher quand ils sont utilisés? Et tout programme dépendant du traitement des ordures en temps voulu au lieu de dépendre de la pression de la mémoire vit de toute façon dans un état de péché, même s’il bouge.
Déduplicateur
4

Permettez-moi de suggérer une reformulation et une généralisation de votre question:

Pourquoi Java ne donne-t-il pas de fortes garanties quant à son processus de GC?

Dans cet esprit, parcourez rapidement les réponses. Il y en a sept jusqu'à présent (sans compter celui-ci), avec pas mal de fils de commentaires.

C'est ta réponse.

GC est difficile. Il y a beaucoup de considérations, beaucoup de compromis différents et, finalement, beaucoup d'approches très différentes. Certaines de ces approches permettent de gérer un objet dès qu'il n'est pas nécessaire. d'autres non. En gardant le contrat vacant, Java offre plus d'options à ses développeurs.

Bien sûr, il y a un compromis même dans cette décision: en gardant le contrat en suspens, Java * supprime la possibilité pour les programmeurs de pouvoir compter sur des destructeurs. C'est quelque chose que les programmeurs C ++ en particulier manquent souvent ([citation nécessaire];)), donc ce n'est pas un compromis insignifiant. Je n'ai pas assisté à une discussion de cette méta-décision particulière, mais les gens de Java ont sans doute décidé que les avantages de disposer de davantage d'options de GC dépassaient les avantages de pouvoir dire aux programmeurs exactement quand un objet serait détruit.


* Il y a la finalizeméthode, mais pour diverses raisons qui sont hors de portée pour cette réponse, il est difficile et peu recommandable de s'y fier.

yshavit
la source
3

Il existe deux stratégies différentes de gestion de la mémoire sans code explicite écrit par le développeur: la récupération de place et le comptage de références.

La récupération de place a l'avantage de "fonctionner" sauf si le développeur fait quelque chose de stupide. Avec le comptage de références, vous pouvez avoir des cycles de référence, ce qui signifie que cela "fonctionne" mais que le développeur doit parfois être intelligent. C'est donc un avantage pour la collecte des ordures.

Avec le comptage de références, l'objet s'éloigne immédiatement lorsque le comptage de références tombe à zéro. C'est un avantage pour le comptage de références.

Speedwise, le ramassage des ordures est plus rapide si vous croyez les fans de ce dernier et le décompte des références est plus rapide si vous croyez les fans du décompte des références.

Il existe juste deux méthodes différentes pour atteindre le même objectif, Java en a choisi une, Objective-C en a choisi une autre (et beaucoup de support du compilateur lui a été ajouté pour le transformer en une tâche fastidieuse pour les développeurs).

Passer de la récupération de place au dépouillement des références en Java serait une entreprise majeure, car de nombreux changements de code seraient nécessaires.

En théorie, Java aurait pu implémenter un mélange de récupération de place et de comptage de références: si le nombre de références est 0, l'objet est inaccessible, mais pas nécessairement l'inverse. Vous pouvez donc conserver les comptes de référence et supprimer des objets lorsque leur nombre de référence est égal à zéro (puis exécuter une récupération de place de temps en temps pour intercepter des objets dans des cycles de référence inaccessibles). Je pense que le monde est divisé en 50/50 pour ceux qui pensent que l'ajout du décompte de références au ramassage des ordures est une mauvaise idée, et ceux qui pensent que l'ajout du ramassage des ordures au décompte des références est une mauvaise idée. Donc, cela ne va pas arriver.

Ainsi, Java peut supprimer des objets immédiatement si leur nombre de références est égal à zéro et supprimer des objets dans des cycles inaccessibles ultérieurement. Mais c'est une décision de conception et Java a décidé de ne pas la prendre.

gnasher729
la source
Avec le comptage de références, la finalisation est triviale, le programmeur s’occupant des cycles. Avec gc, les cycles sont triviaux, mais le programmeur doit faire preuve de prudence lors de la finalisation.
Déduplicateur
@Deduplicator En Java, il est également possible de créer de solides références à des objets en cours de finalisation ... En Objective-C et Swift, une fois que le compte de référence est égal à zéro, l'objet va disparaître (sauf si vous mettez une boucle infinie dans dealloc / déiste).
gnasher729
Je viens de remarquer un vérificateur d'orthographe stupide remplaçant deinit par un déiste ...
gnasher729
1
Il y a une raison pour laquelle la plupart des programmeurs détestent la correction orthographique automatique ... ;-)
Déduplicateur
lol ... Je pense que le monde est divisé 0.1 / 0.1 / 99.8 entre ceux qui pensent que l'ajout du nombre de références au ramassage des ordures est une mauvaise idée et ceux qui pensent que l'ajout du ramassage des ordures au décompte des références est une mauvaise idée continuez à compter des jours jusqu'à ce que la collecte des ordures arrive car cette tonne
sent de
1

Tous les autres arguments de performance et discussions sur la difficulté de comprendre lorsqu'il n'y a plus de références à un objet sont corrects, bien qu'une autre idée qui mérite d'être mentionnée mérite d'être mentionnée, c'est qu'il existe au moins une machine virtuelle (azul) prenant en compte quelque chose comme ceci. en ce sens qu'il implémente parallel gc qui a essentiellement un thread vm vérifiant en permanence les références pour tenter de les supprimer, ce qui n'agira pas de manière totalement différente de ce dont vous parlez. Fondamentalement, il va constamment regarder le tas et essayer de récupérer toute mémoire qui n'est pas référencée. Cela engendre un coût de performance très léger, mais conduit à des temps de CPG essentiellement nuls ou très courts. (C'est-à-dire que si la taille du segment de mémoire en expansion constante dépasse la mémoire vive du système, Azul devient confus, puis il y a des dragons)

TLDR Quelque chose comme ça existe un peu pour la JVM, c'est juste une JVM spéciale et elle a des inconvénients comme n'importe quel autre compromis d'ingénierie.

Disclaimer: Je n'ai aucun lien avec Azul, nous l'avons utilisé lors d'un précédent emploi.

ford prefet
la source
1

L'optimisation du débit soutenu ou la minimisation de la latence de la génératrice sont en tension dynamique, ce qui est probablement la raison la plus courante pour laquelle la GC n'est pas immédiate. Dans certains systèmes, tels que les applications d'urgence 911, le non-respect d'un seuil de latence spécifique peut déclencher des processus de basculement du site. Dans d’autres, comme les sites bancaires et / ou d’arbitrage, il est bien plus important de maximiser le débit.

barmid
la source
0

La vitesse

Pourquoi tout cela se passe-t-il finalement à cause de la vitesse. Si les processeurs étaient infiniment rapides ou (pour être pratique) proches, par exemple, 1 000 000 000 000 000 000 000 000 000 000 000 000 000 opérations par seconde, des opérations extrêmement longues et compliquées peuvent se produire entre chaque opérateur, telles que la suppression d'objets supprimés. Comme ce nombre d'opérations par seconde n'est pas exact et que, comme l'expliquent la plupart des autres réponses, il est en réalité compliqué et nécessite beaucoup de ressources pour le résoudre, il existe un garbage collection qui permet aux programmes de se concentrer sur ce qu'ils essaient réellement d'accomplir. manière rapide.

Michael Durrant
la source
Eh bien, je suis sûr que nous trouverions des moyens plus intéressants d’utiliser les cycles supplémentaires que cela.
Déduplicateur