D'après ma compréhension, la récupération de place dans Java nettoie certains objets si rien d'autre ne "pointe" vers cet objet.
Ma question est, que se passe-t-il si nous avons quelque chose comme ceci:
class Node {
public object value;
public Node next;
public Node(object o, Node n) { value = 0; next = n;}
}
//...some code
{
Node a = new Node("a", null),
b = new Node("b", a),
c = new Node("c", b);
a.next = c;
} //end of scope
//...other code
a
, b
et c
devraient être récupérés, mais ils sont tous référencés par d'autres objets.
Comment la Java garbage collection traite-t-elle cela? (Ou s'agit-il simplement d'une perte de mémoire?)
Le GC de Java considère les objets comme des "déchets" s'ils ne sont pas accessibles via une chaîne commençant à la racine du récupérateur de déchets, de sorte que ces objets seront collectés. Même si les objets peuvent se rejoindre pour former un cycle, ils restent des ordures s'ils sont coupés de la racine.
Reportez-vous à la section sur les objets inaccessibles de l'Annexe A: La vérité sur le garbage collection dans Performances de la plate-forme Java: stratégies et tactiques pour les détails sanglants.
yes Java Garbage Collector gère les références circulaires!)
How?
Il existe des objets spéciaux appelés racines de récupération de place (racines GC). Ceux-ci sont toujours accessibles, de même que tout objet qui les a à sa propre racine.
Une simple Java a les racines GC suivantes:
Pour déterminer quels objets ne sont plus utilisés, la machine virtuelle Java exécute par intermittence ce que l’on appelle très justement un algorithme de balisage . Cela fonctionne comme suit
Ainsi, si un objet n'est pas accessible depuis les racines du GC (même s'il est auto-référencé ou référencé de manière cyclique), il sera soumis à un nettoyage de la mémoire.
Bien sûr, cela peut parfois entraîner une fuite de mémoire si le programmeur oublie de déréférencer un objet.
Source: Gestion de la mémoire Java
Un ramasse-miettes commence à partir d'un ensemble d'emplacements "racine" toujours considérés comme "accessibles", tels que les registres de la CPU, la pile et les variables globales. Cela fonctionne en trouvant des indications dans ces domaines et en recherchant récursivement tout ce à quoi ils pointent. Une fois que tout est trouvé, tout sinon c'est de la foutaise.
Il existe bien sûr de nombreuses variantes, principalement pour des raisons de rapidité. Par exemple, la plupart des éboueurs modernes sont "générationnels", ce qui signifie qu'ils divisent les objets en générations et que, lorsqu'un objet vieillit, il devient de plus en plus long à chaque fois qu'il essaie de déterminer si cet objet est toujours valide ou non. - on commence à croire que, s'il a vécu longtemps, il y a de bonnes chances qu'il continue à vivre encore plus longtemps.
Néanmoins, l'idée de base reste la même: tout est basé sur le fait de partir d'un ensemble fondamental d'éléments pris pour acquis, puis de poursuivre tous les indicateurs pour trouver ce qui pourrait être utilisé.
Intéressant à part: peut-être que les gens sont souvent surpris par le degré de similitude entre cette partie d'un ramasse-miettes et le code de marshaling pour des objets tels que les appels de procédure à distance. Dans chaque cas, vous partez d'un ensemble d'objets racine et cherchez des pointeurs pour trouver tous les autres objets auxquels il fait référence ...
Vous avez raison. La forme spécifique de garbage collection que vous décrivez s'appelle " comptage de références ". La façon dont cela fonctionne (du moins conceptuellement, la plupart des implémentations modernes du comptage de références sont en réalité mises en œuvre de manière très différente) dans le cas le plus simple, ressemble à ceci:
Et cette stratégie simple a exactement le problème que vous décrivez: si A fait référence à B et B à A, leurs deux comptes de référence peuvent ne jamais être inférieurs à 1 , ce qui signifie qu'ils ne seront jamais collectés.
Il y a quatre façons de traiter ce problème:
À propos, l’autre autre moyen d’implémenter un ramasse-miettes (et j’ai déjà fait allusion à cela quelques fois ci-dessus), est traçage . Un collecteur de traçage est basé sur le concept d'accessibilité . Vous commencez avec un ensemble de racines que vous savez être toujours accessible ( les constantes globales, par exemple, ou la classe Object
, la portée lexicale actuelle, le cadre de pile actuel) et à partir de là, vous tracez tous les objets accessibles à partir du jeu de racines, puis tous les objets accessibles à partir des objets accessibles à partir du jeu de racines, et ainsi de suite, jusqu'à la fermeture transitive. Tout ce qui est pas dans cette fermeture est de la foutaise.
Puisqu'un cycle est seulement accessible en lui-même, mais pas accessible à partir du jeu de racines, il sera collecté.
Les GC Java ne se comportent pas vraiment comme vous le décrivez. Il est plus précis de dire qu’ils partent d’un ensemble d’objets de base, souvent appelés "racines GC", et collecteront tout objet pouvant ne pas être atteint à partir d'une racine.
Les racines du GC comprennent des éléments tels que:
Ainsi, dans votre cas, une fois que les variables locales a, b et c sont hors de portée à la fin de votre méthode, il n’y a plus de racines GC qui contiennent, directement ou indirectement, une référence à l’un de vos trois nœuds et ils seront éligibles pour la collecte des ordures.
Le lien de TofuBeer contient plus de détails si vous le souhaitez.
Cet article (non disponible) va en profondeur sur le ramasse-miettes (conceptuellement ... il y a plusieurs implémentations). La partie pertinente de votre message est "A.3.4 Injoignable":
A.3.4 Injoignable Un objet entre dans un état inaccessible lorsqu'il n'existe plus aucune référence forte à cet objet. Lorsqu'un objet est inaccessible, il est candidat à la collecte. Notez le libellé: Le fait qu'un objet soit candidat à la collecte ne signifie pas qu'il sera immédiatement collecté. La machine virtuelle Java est libre de retarder la collecte jusqu'à ce qu'il y ait un besoin immédiat de mémoire consommée par l'objet.
La récupération de place ne signifie généralement pas "nettoyer un objet si et rien d'autre ne" pointe "vers cet objet" (c'est le comptage de références). Le ramassage des ordures signifie en gros rechercher des objets auxquels le programme ne permet pas d'accéder.
Ainsi, dans votre exemple, après que a, b et c aient quitté la portée, ils peuvent être collectés par le GC, car vous ne pouvez plus accéder à ces objets.
Bill a répondu à votre question directement. Comme Amnon l'a dit, votre définition du ramassage des ordures n'est qu'un comptage de références. Je voulais juste ajouter que même des algorithmes très simples tels que mark, sweep and copy collection gèrent facilement les références circulaires. Donc, rien de magique à ce sujet!