L'utilisation de final for variables dans Java améliore-t-elle le garbage collection?


86

Aujourd'hui, mes collègues et moi avons une discussion sur l'utilisation du finalmot - clé en Java pour améliorer le ramasse-miettes.

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 finalaiderait 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?


il y a deux choses ici, en fait. 1) lorsque vous écrivez dans un champ local de méthode et une instance. Lorsque vous écrivez sur une instance, il peut y avoir un avantage.
Eugene

Réponses:


86

Voici un exemple légèrement différent, un avec des champs de type référence finaux 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 de MyOtherObject, et le GC devra suivre ce lien pour rechercher des objets en direct.

La JVM utilise un algorithme GC mark-sweep, qui doit examiner toutes les références en direct dans les emplacements GC "racine" (comme tous les objets de la pile d'appels actuelle). Chaque objet vivant est «marqué» comme vivant, et tout objet auquel un objet vivant fait référence 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 est partitionnée en une «jeune génération» et une «ancienne génération». Tous les objets sont initialement attribués à la jeune génération (parfois appelée «la crèche»). 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ée «génération titulaire»), 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 en Java est d'éliminer les fausses références le plus rapidement possible. Vous pouvez le faire en attribuant «null» à une référence d'objet dès que vous avez fini de l'utiliser.

Ou, mieux encore, minimisez la taille de chaque portée 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 fermeture 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 fait nécessaire.

Si vous utilisez de petites méthodes, avec seulement une douzaine de lignes de code, alors les objets déclarés dans cette méthode tomberont plus rapidement hors de portée, et le GC sera en mesure de faire l'essentiel de son travail dans un environnement beaucoup plus efficace. jeune génération. Vous ne voulez pas que les objets soient déplacés vers l'ancienne génération à moins que cela ne soit absolument nécessaire.


Nourriture pour la pensée. J'ai toujours pensé que le code en ligne était plus rapide, mais si jvm manque de mémoire, il ralentira également. hmmmmm ...
WolfmanDragon

1
Je suppose juste ici ... mais je suppose que le compilateur JIT peut incorporer des valeurs primitives finales (pas des objets), pour des augmentations de performances modestes. Le code inlining, en revanche, est capable de produire des optimisations significatives, mais n'a rien à voir avec les variables finales.
benjismith

2
Il ne serait pas possible d'attribuer null à un objet final déjà créé, alors peut-être final au lieu d'aide, pourrait rendre les choses plus difficiles
Hernán Eche

1
On pourrait également utiliser {} pour limiter la portée d'une méthode volumineuse, plutôt que de la diviser en plusieurs méthodes privées qui pourraient ne pas être pertinentes pour d'autres méthodes de classe.
mmm

Vous pouvez également utiliser des variables locales au lieu de champs lorsque cela est possible afin d'améliorer le garbage collection de la mémoire et de réduire les liens référentiels.
sivi

37

La déclaration d'une variable locale finaln'affectera pas le garbage collection, 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 totalWeightqui a été marquée final. D'un autre côté, déclarer une primitive ( doubleau lieu de Double) finalpermettra à cette variable d'être incorporée dans le code appelant, ce qui pourrait entraîner une amélioration de la mémoire et des performances. Ceci est utilisé lorsque vous en avez plusieurs public static final Stringsdans une classe.

En général, le compilateur et le runtime optimiseront là 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. À utiliser finallorsque 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 problème réel.


26

Non, ce n'est absolument pas vrai.

N'oubliez pas que finalcela 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érifié pour voir si elle a changé) mais ce serait si mineur que de ne pas s'inquiéter.

Final devraient être considérées comme des métadonnées utiles pour le développeur et non comme une optimisation du compilateur.


17

Quelques points à éclaircir:

  • L'annulation de la référence ne devrait pas aider GC. Si c'est le cas, cela indiquerait que vos variables sont surdimensionnées. 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 final 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.


Il y a une allocation sur pile (de primitives et de références aux objets dans le tas) dans java: stackoverflow.com/a/8061692/32453 Java exige que les objets dans la même fermeture qu'une classe / lambda anonyme soient également c'est juste pour réduire la "mémoire requise / frame" / réduire la confusion donc n'est pas lié à sa collecte ...
rogerdpack

11

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 de valeurs Boxed plutôt que de 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.


1
Tu as raison. Je n'avais besoin que d'un exemple rapide pour expliquer ma question.
Goran Martinic

5

Les variables finales ne peuvent pas être modifiées après l'affectation initiale (imposée par le compilateur).

Cela ne modifie 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 le garbage collection dans des situations de mémoire serrée).

Vous devez savoir que final permet au compilateur de faire des hypothèses sur ce qu'il faut optimiser. Code incorporé 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.


@Eugene Dépend de votre gestionnaire de sécurité et si le compilateur a incorporé la variable ou non.
Thorbjørn Ravn Andersen

à droite, j'étais juste pédant; rien de plus; a également fourni une réponse aussi
Eugene

4

Il existe un cas de coin moins connu avec les ramasseurs d'ordures générationnels. (Pour une brève description, lisez la réponse de 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 scanné pour les références, puis les objets de la jeune génération sont scanné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 de longue durée (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 collecteur JVM hotspot ), affectant en fait les performances du GC.

La raison pour laquelle un ancien objet ne peut pas faire référence à un objet 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, il ne sera pas marqué et serait à tort désalloué pendant la phase de balayage.

Bien sûr, comme beaucoup l'ont souligné, le mot-clé final n'affecte pas vraiment le ramasse-miettes, mais il garantit que la référence ne sera jamais transformée en un objet plus jeune si cet objet survit aux collections mineures et parvient à l'ancien tas.

Des articles:

IBM sur le garbage collection: historique , dans la JVM hotspot et performances . Celles-ci ne sont peut-être plus entièrement valides, car elles remontent à 2003/04, mais elles donnent un aperçu facile à lire des CG.

Sun on Tuning garbage collection


3

GC agit sur les références inaccessibles. Cela n'a rien à voir avec le terme «final», qui est simplement une affirmation de cession unique. Est-il possible que certains GC de VM puissent utiliser "final"? Je ne vois ni comment ni pourquoi.


3

finalsur les variables et les 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 (il peut annuler plus tard si une classe qui rompt cette hypothèse est chargée). Je crois que finalles méthodes sont sensiblement les mêmes que les classes. finalsur un champ statique peut permettre à la variable d'être interprétée comme une "constante de compilation" et l'optimisation doit être effectuée par javac sur cette base. finalsur les champs permet à la JVM une certaine liberté d'ignorer les relations qui se produisent avant .


2

Il semble y avoir beaucoup de réponses qui sont des conjectures errantes. La vérité est qu'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.


Cela peut être vrai, mais le compilateur peut toujours utiliser les informations finales pendant l'analyse du flux de données.

@WernerVanBelle le compilateur sait déjà qu'une variable n'est définie qu'une seule fois. Il doit déjà effectuer une analyse de flux de données pour savoir qu'une variable peut être nulle, n'est pas initialisée avant utilisation, etc. Ainsi, les finales locales n'offrent aucune nouvelle information au compilateur.
Matt Quigley

Ce n'est pas le cas. L'analyse de flux de données peut en déduire beaucoup de choses, mais il est possible d'avoir un programme complet à l'intérieur d'un bloc qui définira ou non une variable locale. Le compilateur ne peut pas savoir à l'avance si la variable sera écrite et sera constante du tout. Par conséquent, le compilateur, sans le mot clé final, ne peut pas garantir qu'une variable est finale ou non.

@WernerVanBelle Je suis vraiment intriguée, pouvez-vous donner un exemple? Je ne vois pas comment il peut y avoir une affectation finale à une variable non finale que le compilateur ne connaît pas. Le compilateur sait que si vous avez une variable non initialisée, il ne vous laissera pas l'utiliser. Si vous essayez d'assigner une dernière variable à l'intérieur d'une boucle, le compilateur ne vous le laissera pas. Quel est un exemple où une variable qui PEUT être déclarée comme finale, mais qui n'est PAS, et le compilateur ne peut pas garantir qu'elle est finale? Je soupçonne que tout exemple serait une variable qui ne pourrait pas être déclarée comme finale en premier lieu.
Matt Quigley

Ah après avoir relu votre commentaire, je vois que votre exemple est une variable initialisée à l'intérieur d'un bloc conditionnel. Ces variables ne peuvent pas être définitives en premier lieu, à moins que tous les chemins de condition n'initialisent la variable une fois. Ainsi, le compilateur connaît de telles déclarations - c'est ainsi qu'il vous permet de compiler une variable finale initialisée à deux endroits différents (pensez final int x; if (cond) x=1; else x=2;). Le compilateur, sans le mot clé final, peut donc garantir qu'une variable est définitive ou non.
Matt Quigley

1

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 surcharge des membres de la superclasse, nous pouvons les déclarer comme finales en utilisant le mot-clé final. Par exemple, final int a=10; final void display(){......} rendre une méthode définitive 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.


1

Strictement parlant des champs d' instance , final pourrait légèrement améliorer les performances si un GC particulier veut exploiter cela. Lorsqu'un concurrent GCse produit (cela signifie que votre application est toujours en cours d'exécution, alors que GC est en cours ), voyez ceci pour une explication plus large , les GC doivent utiliser certaines barrières lorsque les écritures et / ou les lectures sont effectuées. Le lien que je vous ai donné explique à peu près cela, mais pour être très bref: quand a GCfait un travail simultané, tout lit et écrit dans le tas (pendant que le GC est en cours), est "intercepté" et appliqué plus tard dans le temps; afin que la phase concurrente du GC puisse terminer son travail.

Par finalexemple les champs, puisqu'ils ne peuvent pas être modifiés (sauf réflexion), ces barrières peuvent être omises. Et ce n'est pas qu'une pure théorie.

Shenandoah GCles a en pratique (mais pas pour longtemps ), et vous pouvez faire, par exemple:

-XX:+UnlockExperimentalVMOptions  
-XX:+UseShenandoahGC  
-XX:+ShenandoahOptimizeInstanceFinals

Et il y aura des optimisations dans l'algorithme GC qui le rendront légèrement plus rapide. C'est parce qu'il n'y aura pas de barrières interceptées final, puisque personne ne devrait jamais les modifier. Pas même par réflexion ou JNI.


0

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, ainsi vous vous retrouverez sans mémoire allouée.


0

absolument, tant que la durée de vie de l'objet est plus courte, ce qui donne un grand avantage à 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, JVM lève outofmemoryerror lors du premier test et JVM s'est arrêté. mais dans le deuxième test, réussi à obtenir le rapport grâce à une meilleure gestion de la mémoire.


0

Le seul moment où je préfère déclarer les variables locales comme finales est lorsque:

  • Je dois les rendre définitifs pour qu'ils puissent être partagés avec une classe anonyme (par exemple: créer un thread démon et lui permettre d'accéder à une valeur de la méthode englobante)

  • Je veux les rendre définitifs (par exemple: une valeur qui ne devrait pas / ne doit pas être remplacée par erreur)

Aident-ils à la collecte rapide des déchets?
AFAIK un objet devient un candidat de la collection GC s'il n'a aucune référence forte et dans ce cas également, il n'y a aucune garantie qu'ils seront immédiatement ramassés. En général, on dit qu'une référence forte meurt lorsqu'elle sort de la portée ou que l'utilisateur la réaffecte explicitement à une référence nulle, ainsi, les déclarer finales signifie que la référence continuera d'exister jusqu'à ce que la méthode existe (à moins que sa portée ne soit explicitement réduite à un bloc interne spécifique {}) parce que vous ne pouvez pas réaffecter les variables finales (c'est-à-dire ne peut pas réaffecter à null). Donc, je pense que Wrt Garbage Collection «final» peut introduire un retard possible indésirable, il faut donc être peu prudent en définissant leur portée car cela contrôle le moment où ils deviendront candidats pour GC.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.