58 votes

Le GC de la JVM peut-il déplacer des objets au milieu d'une comparaison de références, provoquant l'échec de la comparaison même si les deux parties font référence au même objet ?

Il est bien connu que les GC déplacent parfois des objets dans la mémoire. Et je crois savoir que tant que toutes les références sont mises à jour lorsque l'objet est déplacé (avant qu'un quelconque code utilisateur ne soit appelé), cela devrait être parfaitement sûr.

Cependant, j'ai vu quelqu'un mentionner que la comparaison de références pouvait être dangereuse parce que l'objet était déplacé par le GC au milieu d'une comparaison de références, de sorte que la comparaison pouvait échouer même si les deux références devaient se référer au même objet ?

Par exemple, existe-t-il une situation dans laquelle le code suivant n'afficherait pas "vrai" ?

Foo foo = new Foo();
Foo bar = foo;
if(foo == bar) {
    System.out.println("true");
}

J'ai essayé de faire une recherche sur Google et le manque de résultats fiables me pousse à croire que la personne qui a déclaré cela s'est trompée, mais j'ai trouvé un assortiment de messages de forum ( comme celui-ci ) qui semblait indiquer qu'il avait raison. Mais dans ce fil de discussion, il y a aussi des gens qui disent que ça ne devrait pas être le cas.

47voto

mhlz Points 2225

Les instructions du bytecode Java sont toujours atomiques par rapport au GC (c'est-à-dire qu'aucun cycle ne peut se produire pendant l'exécution d'une seule instruction).

Le seul moment où le GC s'exécutera est entre deux instructions Bytecode.

En regardant le bytecode que javac génère pour l'instruction if dans votre code, nous pouvons simplement vérifier si un GC aurait un quelconque effet :

// a GC here wouldn't change anything
ALOAD 1
// a GC cycle here would update all references accordingly, even the one on the stack
ALOAD 2
// same here. A GC cycle will update all references to the object on the stack
IF_ACMPNE L3
// this is the comparison of the two references. no cycle can happen while this comparison
// "is running" so there won't be any problems with this either

De plus, même si la GC était capable de fonctionner pendant l'exécution d'une instruction de bytecode, les références de l'objet ne changeraient pas. C'est toujours le même objet avant et après le cycle.

Donc, en résumé, la réponse à votre question est non, elle sera toujours vraie.

37voto

Erik Nyström Points 456

Source :

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3

La réponse courte est de regarder les spécifications de Java 8 : Non.

Le site == effectuera toujours une vérification de l'égalité des objets (étant donné qu'aucune référence n'est nulle). Même si l'objet est déplacé, l'objet est toujours le même .

Si vous constatez un tel effet, vous venez de trouver un Bogue de la JVM . Allez le soumettre.

Il se peut, bien sûr, qu'une implémentation obscure de la JVM n'applique pas cette règle pour une raison étrange de performance. Si c'est le cas, il serait sage de simplement abandonner cette JVM...

12voto

some Folk Points 3860

TL;DR

Tu ne devrais pas penser à ce genre de choses, c'est un endroit sombre. Java a clairement indiqué ses spécifications et vous ne devriez pas en douter, jamais.

2.7. Représentation des objets

La machine virtuelle Java n'impose pas de structure interne particulière pour les objets.

Source : JVMS SE8.

J'en doute ! Si vous doutez de cet opérateur de base, vous risquez de douter de tout le reste, de devenir frustré et paranoïaque avec des problèmes de confiance, ce n'est pas l'endroit où vous voulez être.

Et si ça m'arrive à moi ? Un tel bug ne devrait pas exister. La discussion sur Oracle que vous avez fournie rapporte un bogue qui s'est produit il y a des années et, d'une manière ou d'une autre, l'OP a décidé de le faire apparaître sans raison, sans documentation fiable sur l'existence d'un tel bogue de nos jours. Cependant, si ce bogue ou tout autre bogue vous est arrivé, veuillez le soumettre. aquí .

Pour dissiper vos inquiétudes, Java a adapté l'approche pointeur à pointeur dans la JVM table des pointeurs vous pouvez en savoir plus sur son efficacité. aquí .

11voto

the8472 Points 588

Les GC ne se produisent qu'à des moments du programme où l'état est bien défini et où la JVM sait exactement où tout se trouve dans les registres/la pile/le tas, de sorte que toutes les références peuvent être corrigées lorsqu'un objet est déplacé.

C'est-à-dire qu'ils ne peuvent pas se produire entre l'exécution d'instructions d'assemblage arbitraires. Conceptuellement, on peut penser qu'ils se produisent entre les instructions de bytecode de la JVM, le GC ajustant toutes les références qui ont été générées par les instructions précédentes.

5voto

Holger Points 13789

Vous posez une question avec une fausse prémisse. Puisque le == ne compare pas les emplacements de mémoire, il n'est pas sensible aux changements d'emplacement de mémoire. en soi . Le site == L'opérateur, appliqué aux références, compare les identité des objets référencés, quelle que soit la manière dont la JVM l'implémente.

Pour citer un exemple qui va à l'encontre de la compréhension habituelle, une JVM distribuée peut avoir des objets conservés dans la RAM de différents ordinateurs, y compris la possibilité de copies locales. Il ne suffit donc pas de comparer les adresses. Bien entendu, c'est à l'implémentation de la JVM de s'assurer que la sémantique, telle que définie dans la spécification du langage Java, ne change pas.

Si une implémentation particulière de la JVM met en œuvre une comparaison de références en comparant directement les emplacements mémoire des objets et possède un ramasseur d'ordures qui peut modifier les emplacements de la mémoire, bien sûr, c'est à la JVM de s'assurer que ces deux fonctionnalités ne peuvent pas interférer l'une avec l'autre de manière incompatible.

Si vous êtes curieux de savoir comment cela peut fonctionner, par exemple dans un code optimisé et compilé en JIT, la granularité n'est pas aussi fine que vous pourriez le penser. Tout code séquentiel, y compris les branches avant, peut être considéré comme s'exécutant suffisamment vite pour permettre de retarder le ramassage des ordures jusqu'à son achèvement. Ainsi, le garbage collection ne peut pas se produire à n'importe quel moment dans du code optimisé, mais doit être autorisé à certains moments, par ex.

  • branches arrière (notez qu'en raison du déroulement de la boucle, chaque itération de boucle n'implique pas une branche arrière)
  • allocations de mémoire
  • actions de synchronisation des fils
  • invocation d'une méthode qui n'a pas été inlined/analysée
  • peut-être quelque chose de spécial, j'ai oublié

La JVM émet donc du code contenant certains "points sûrs" où l'on sait, quelles références sont actuellement détenues, comment les remplacer, si nécessaire et, bien sûr, le changement d'emplacement n'a aucun impact sur la correction. Entre ces points, le code peut s'exécuter sans avoir à se soucier de la possibilité de changer d'emplacement mémoire, tandis que le ramasseur de déchets attendra que le code atteigne un point sûr en cas de besoin, ce qui est garanti dans un temps fini, plutôt court.

Mais, comme nous l'avons dit, ce sont des détails de mise en œuvre. Au niveau formel, des choses comme la modification des emplacements mémoire n'existent pas, il n'est donc pas nécessaire de spécifier explicitement qu'elles ne sont pas autorisées à modifier la sémantique du code Java. Aucun détail d'implémentation n'est autorisé à le faire.

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