Aujourd'hui, mes collègues et moi avons une discussion sur l'utilisation du mot clé final
dans Java pour améliorer la collecte des ordures).
Par exemple, si vous écrivez une méthode comme:
public Double doCalc(final Double value)
{
final Double maxWeight = 1000.0;
final Double totalWeight = maxWeight * value;
return totalWeight;
}
La déclaration des variables dans la méthode final
aiderait le garbage collection à nettoyer la mémoire des variables inutilisées dans la méthode après la fin de la méthode.
Est-ce vrai?
Voici un exemple légèrement différent, un avec des champs de type référence finale plutôt que des variables locales de type valeur finale:
public class MyClass {
public final MyOtherObject obj;
}
Chaque fois que vous créez une instance de MyClass, vous créez une référence sortante à une instance MyOtherObject, et le GC devra suivre ce lien pour rechercher des objets vivants.
La JVM utilise un algorithme GC à balayage de marque, qui doit examiner tous les référentiels actifs dans les emplacements "racine" du GC (comme tous les objets de la pile d'appels actuelle). Chaque objet vivant est "marqué" comme vivant, et tout objet auquel fait référence un objet vivant est également marqué comme vivant.
Une fois la phase de marquage terminée, le GC parcourt le tas, libérant de la mémoire pour tous les objets non marqués (et compactant la mémoire pour les objets vivants restants).
En outre, il est important de reconnaître que la mémoire de tas Java Java est partitionnée en une "jeune génération" et une "ancienne génération". Tous les objets sont initialement alloués à la jeune génération (parfois appelée " la pépinière "). Comme la plupart des objets sont de courte durée, le GC est plus agressif pour libérer les déchets récents de la jeune génération. Si un objet survit à un cycle de collecte de la jeune génération, il est déplacé dans l'ancienne génération (parfois appelé "génération de longue durée"), qui est traitée moins fréquemment.
Donc, du haut de ma tête, je vais dire "non, le modificateur" final "n'aide pas le GC à réduire sa charge de travail".
À mon avis, la meilleure stratégie pour optimiser votre gestion de la mémoire dans Java est d'éliminer les références parasites le plus rapidement possible. Vous pouvez le faire en affectant "null" à une référence d'objet dès que vous avez fini de l'utiliser.
Ou, mieux encore, minimisez la taille de chaque étendue de déclaration. Par exemple, si vous déclarez un objet au début d'une méthode de 1000 lignes, et si l'objet reste en vie jusqu'à la fin de la portée de cette méthode (la dernière accolade fermante), alors l'objet peut rester en vie beaucoup plus longtemps qu'en réalité nécessaire.
Si vous utilisez de petites méthodes, avec seulement une douzaine de lignes de code, les objets déclarés dans cette méthode tomberont hors de portée plus rapidement, et le GC sera en mesure de faire la plupart de son travail au sein de la beaucoup plus efficace jeune génération. Vous ne voulez pas que des objets soient déplacés dans l'ancienne génération, sauf si cela est absolument nécessaire.
La déclaration d'une variable locale final
n'affectera pas la récupération de place, cela signifie seulement que vous ne pouvez pas modifier la variable. Votre exemple ci-dessus ne doit pas être compilé car vous modifiez la variable totalWeight
qui a été marquée final
. D'un autre côté, déclarer une primitive (double
au lieu de Double
) final
permettra à cette variable d'être insérée dans le code appelant, ce qui pourrait entraîner de la mémoire et des performances amélioration. Ceci est utilisé lorsque vous avez un certain nombre de public static final Strings
dans une classe.
En général, le compilateur et le runtime optimisent où ils le peuvent. Il est préférable d'écrire le code de manière appropriée et de ne pas essayer d'être trop délicat. Utilisez final
lorsque vous ne souhaitez pas que la variable soit modifiée. Supposons que toutes les optimisations faciles seront effectuées par le compilateur et si vous êtes préoccupé par les performances ou l'utilisation de la mémoire, utilisez un profileur pour déterminer le vrai problème.
Non, ce n'est absolument pas vrai.
N'oubliez pas que final
ne signifie pas constant, cela signifie simplement que vous ne pouvez pas modifier la référence.
final MyObject o = new MyObject();
o.setValue("foo"); // Works just fine
o = new MyObject(); // Doesn't work.
Il peut y avoir une petite optimisation basée sur la connaissance que la JVM n'aura jamais à modifier la référence (comme ne pas avoir à vérifier si elle a changé) mais ce serait si mineur qu'il ne faut pas s'inquiéter.
Final
doit être considéré comme des métadonnées utiles pour le développeur et non comme une optimisation du compilateur.
Quelques points à clarifier:
La suppression de la référence ne devrait pas aider le GC. Si c'était le cas, cela indiquerait que vos variables sont trop étendues. Une exception est le cas du népotisme d'objet.
Il n'y a pas encore d'allocation sur pile en Java.
Déclarer une variable finale signifie que vous ne pouvez pas (dans des conditions normales) attribuer une nouvelle valeur à cette variable. Puisque final ne dit rien sur la portée, il ne dit rien sur son effet sur GC.
Eh bien, je ne connais pas l'utilisation du modificateur "final" dans ce cas, ni son effet sur le GC.
Mais je peux vous dire ceci: votre utilisation des valeurs Boxed plutôt que des primitives (par exemple, Double au lieu de double) allouera ces objets sur le tas plutôt que sur la pile, et produira des déchets inutiles que le GC devra nettoyer.
J'utilise uniquement des primitives encadrées lorsque cela est requis par une API existante ou lorsque j'ai besoin de primitives nullables.
Les variables finales ne peuvent pas être modifiées après l'affectation initiale (imposées par le compilateur).
Cela ne change pas le comportement du garbage collection en tant que tel. La seule chose est que ces variables ne peuvent pas être annulées lorsqu'elles ne sont plus utilisées (ce qui peut aider la récupération de place dans les situations de mémoire limitée).
Vous devez savoir que final permet au compilateur de faire des hypothèses sur ce qu'il faut optimiser. Code en ligne et non compris le code connu pour ne pas être accessible.
final boolean debug = false;
......
if (debug) {
System.out.println("DEBUG INFO!");
}
Le println ne sera pas inclus dans le code d'octet.
GC agit sur les références inaccessibles. Cela n'a rien à voir avec "final", qui est simplement une affirmation d'affectation unique. Est-il possible que certains GC de VM puissent utiliser "final"? Je ne vois ni comment ni pourquoi.
final
sur les variables et paramètres locaux ne fait aucune différence pour les fichiers de classe produits, donc ne peut pas affecter les performances d'exécution. Si une classe n'a pas de sous-classes, HotSpot traite cette classe comme si elle était finale de toute façon (elle peut être annulée plus tard si une classe qui rompt cette hypothèse est chargée). Je crois que final
sur les méthodes est un peu la même chose que les classes. final
sur un champ statique peut permettre à la variable d'être interprétée comme une "constante de temps de compilation" et l'optimisation doit être effectuée par javac sur cette base. final
sur les champs donne à la JVM une certaine liberté pour ignorer les relations arrive-avant.
Il y a un cas d'angle moins bien connu avec les collecteurs d'ordures générationnels. (Pour une brève description, lisez la réponse par benjismith pour un aperçu plus approfondi, lisez les articles à la fin).
L'idée dans les CG générationnels est que la plupart du temps, seules les jeunes générations doivent être prises en compte. L'emplacement racine est analysé pour les références, puis les objets de la jeune génération sont analysés. Lors de ces balayages plus fréquents, aucun objet de l'ancienne génération n'est vérifié.
Maintenant, le problème vient du fait qu'un objet n'est pas autorisé à avoir des références à des objets plus jeunes. Lorsqu'un objet à longue durée de vie (ancienne génération) obtient une référence à un nouvel objet, cette référence doit être explicitement suivie par le garbage collector (voir l'article d'IBM sur le hotspot JVM collector ), affectant en fait le GC performance.
La raison pour laquelle un ancien objet ne peut pas faire référence à un plus jeune est que, comme l'ancien objet n'est pas vérifié dans les collections mineures, si la seule référence à l'objet est conservée dans l'ancien objet, elle ne sera pas marquée et serait incorrecte désalloué pendant la phase de balayage.
Bien sûr, comme beaucoup l'ont souligné, le mot clé final n'affecte pas vraiment le garbage collector, mais il garantit que la référence ne sera jamais changée en un objet plus jeune si cet objet survit aux collections mineures et parvient au tas plus ancien.
Des articles:
IBM sur le garbage collection: historique , dans le hotspot JVM et performance . Celles-ci peuvent ne plus être entièrement valides, car elles remontent à 2003/04, mais elles donnent un aperçu facile à lire des GC.
Soleil allumé Optimisation de la collecte des ordures
Il semble y avoir beaucoup de réponses qui sont des conjectures errantes. La vérité est, il n'y a pas de modificateur final pour les variables locales au niveau du bytecode. La machine virtuelle ne saura jamais que vos variables locales ont été définies comme finales ou non.
La réponse à votre question est un non catégorique.
Toutes les méthodes et variables peuvent être remplacées par défaut dans les sous-classes. Si nous voulons sauver les sous-classes de la substitution des membres de la super-classe, nous pouvons les déclarer comme finales en utilisant le mot-clé final. Par exemple - final int a=10;
final void display(){......}
La finalisation d'une méthode garantit que la fonctionnalité définie dans la superclasse ne sera jamais modifiée de toute façon. De même, la valeur d'une variable finale ne peut jamais être modifiée. Les variables finales se comportent comme des variables de classe.
La seule fois où je préfère déclarer des variables locales comme finales, c'est quand:
I have pour les rendre finaux afin qu'ils puissent être partagés avec une classe anonyme (par exemple: créer un thread démon et le laisser accéder à une valeur de la méthode englobante)
Je veux pour les rendre finales (par exemple: une valeur qui ne devrait pas/ne doit pas être remplacée par erreur)
Aide-t-il à la collecte rapide des ordures?
AFAIK un objet devient candidat à la collecte GC s'il n'a aucune référence forte à lui et dans ce cas également il n'y a aucune garantie qu'ils seront immédiatement récupérés. En général, une référence forte est censée mourir lorsqu'elle sort de la portée ou l'utilisateur la réaffecte explicitement à la référence nulle, donc, la déclarer finale signifie que la référence continuera d'exister jusqu'à ce que la méthode existe (sauf si sa portée est explicitement réduite à un bloc interne spécifique {}) car vous ne pouvez pas réaffecter les variables finales. Je pense donc que w.r.t Garbage Collection 'final' mai introduire un retard possible indésirable.
La seule chose à laquelle je peux penser est que le compilateur pourrait optimiser les variables finales et les incorporer en tant que constantes dans le code, vous vous retrouvez donc sans mémoire allouée.
absolument, tant que la durée de vie de l'objet est plus courte, ce qui offre un grand avantage de la gestion de la mémoire, nous avons récemment examiné la fonctionnalité d'exportation ayant des variables d'instance sur un test et un autre test ayant une variable locale au niveau de la méthode. pendant le test de charge, la JVM jette l'erreur de mémoire au premier test et la JVM s'est arrêtée. mais dans le deuxième test, réussi à obtenir le rapport grâce à une meilleure gestion de la mémoire.