L’utilisation de final pour les variables en Java améliore-t-elle la récupération de place?

Aujourd’hui, mes collègues et moi discutons de l’utilisation du mot clé final en Java pour améliorer la récupération de la mémoire.

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 la récupération de la mémoire à nettoyer la mémoire des variables inutilisées dans la méthode une fois la méthode terminée.

Est-ce vrai?

Voici un exemple légèrement différent, un avec des champs de type référence final 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 vers une occurrence MyOtherObject et le GC doit suivre ce lien pour rechercher des objects actifs.

La JVM utilise un algorithme mark-sweep GC, qui doit examiner toutes les références en direct dans les emplacements “racine” du GC (comme tous les objects de la stack d’appels en cours). Chaque object actif est “marqué” comme étant actif et tout object auquel un object actif fait référence est également marqué comme étant actif.

Une fois la phase de marquage terminée, le CPG parcourt le tas, libérant ainsi de la mémoire pour tous les objects non marqués (et compactant la mémoire pour les objects actifs 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 objects sont initialement atsortingbués à la jeune génération (parfois appelée “la pépinière”). La plupart des objects ayant une durée de vie limitée, le GC est plus agressif quant à la libération des déchets récents de la jeune génération. Si un object survit à un cycle de collecte de la jeune génération, il est transféré dans l’ancienne génération (parfois appelée “génération permanente”), qui est traitée moins fréquemment.

Donc, du haut de la tête, je vais dire “non, le modifer” 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 consiste à éliminer les fausses références le plus rapidement possible. Vous pouvez le faire en assignant “null” à une référence d’object 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 object au début d’une méthode de 1000 lignes et si l’object rest actif jusqu’à la fin de la scope de cette méthode (la dernière accolade fermante), l’object peut restr en vie bien plus longtemps nécessaire.

Si vous utilisez de petites méthodes, avec seulement une douzaine de lignes de code, les objects déclarés dans cette méthode seront plus rapidement hors de scope et le GC sera en mesure de faire la plupart de son travail avec la méthode beaucoup plus efficace. jeune génération. Vous ne voulez pas que les objects soient déplacés dans l’ancienne génération, sauf en cas d’absolue nécessité.

Déclarer 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é lorsque vous modifiez la variable totalWeight qui a été marquée comme final . D’un autre côté, déclarer une primitive ( double au lieu de Double ) va permettre à cette variable d’être incorporée dans le code d’appel, ce qui pourrait entraîner une amélioration de la mémoire et des performances. Ceci est utilisé lorsque vous avez un certain nombre de public static final Ssortingngs dans une classe.

En règle générale, le compilateur et le moteur d’exécution optimisent leur utilisation. Il est préférable d’écrire le code de manière appropriée et de ne pas essayer d’être trop compliqué. Utilisez final lorsque vous ne souhaitez pas que la variable soit modifiée. Supposons que le compilateur optimise toutes les optimisations, 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.

Non, ce n’est absolument pas vrai.

Rappelez-vous que la final ne signifie pas constante, cela signifie simplement que vous ne pouvez pas changer la référence.

 final MyObject o = new MyObject(); o.setValue("foo"); // Works just fine o = new MyObject(); // Doesn't work. 

Il pourrait y avoir une petite optimisation basée sur le fait de savoir que la JVM n’aura jamais à modifier la référence (par exemple, ne pas avoir de vérification pour voir si elle a changé), mais cela ne serait pas grave.

Final doit être considéré comme une méta-donnée utile pour le développeur et non comme une optimisation du compilateur.

Quelques points à éclaircir:

  • L’annulation 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 népotisme d’object.

  • Il n’y a pas d’allocation sur stack pour le moment en Java.

  • Déclarer une variable finale signifie que vous ne pouvez pas (dans des conditions normales) affecter une nouvelle valeur à cette variable. Puisque la finale ne dit rien sur la scope, elle ne dit rien de 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 de valeurs Boxed plutôt que de primitives (par exemple, Double au lieu de double) allouera ces objects au tas plutôt qu’à la stack, et produira des déchets inutiles que le GC devra nettoyer.

J’utilise uniquement des primitives encadrées lorsque requirejs par une API existante, ou lorsque j’ai besoin de primitives pouvant être annulées.

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 de la récupération de place en tant que tel. La seule chose est que ces variables ne peuvent plus ê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 insuffisante).

Vous devez savoir que la version finale permet au compilateur de faire des suppositions sur ce qu’il faut optimiser. Inlining code 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 des réfs 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 pas comment ni pourquoi.

Il existe un cas en coin peu connu avec les collecteurs de déchets générationnels. (Pour une brève description, lisez la réponse de benjismith pour une lecture plus approfondie des articles à la fin).

L’idée des GC de générations est que la plupart du temps, seules les jeunes générations doivent être considérées. L’emplacement racine est analysé à la recherche de références, puis les objects de génération jeune sont analysés. Pendant ce balayage plus fréquent, aucun object de l’ancienne génération n’est vérifié.

Maintenant, le problème vient du fait qu’un object n’est pas autorisé à avoir des références à des objects plus jeunes. Lorsqu’un object de longue durée (ancienne génération) obtient une référence à un nouvel object, cette référence doit être explicitement suivie par le ramasse-miettes (voir l’article d’IBM sur le collecteur JVM de point d’access ), affectant réellement les performances du GC.

La raison pour laquelle un object ancien ne peut pas faire référence à un object plus jeune est que, comme l’ancien object n’est pas vérifié dans des collections mineures, si la seule référence à l’object est conservée dans l’ancien object, il ne sera pas marqué et sera incorrect. désalloué pendant la phase de balayage.

Bien sûr, comme le soulignent de nombreuses personnes, le mot-clé final n’affecte pas vraiment le ramasse-miettes, mais il garantit que la référence ne sera jamais modifiée en un object plus jeune si cet object survit aux anciennes collections.

Des articles:

IBM sur garbage collection: historique , dans la JVM du hotspot et performances . Celles-ci ne sont peut-être plus pleinement valables, car elles datent de 2003-2004, mais elles permettent de mieux comprendre les GC.

Sun on Tuning collecte des ordures

final sur les variables et les parameters locaux ne fait aucune différence par rapport aux fichiers de classe produits et ne peut donc pas affecter les performances à l’exécution. Si une classe n’a pas de sous-classes, HotSpot considère cette classe comme si elle était finale (elle peut annuler plus tard si une classe qui rompt cette hypothèse est chargée). Je crois que les méthodes final sont très similaires aux classes. final on static field peut permettre à la variable d’être interprétée comme une “constante de compilation” et une optimisation par javac sur cette base. final on fields permet à la JVM une certaine marge de manœuvre pour ignorer les relations ” passe-avant” .

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 définitives 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 empêcher les sous-classes de superposer les sous-classes, nous pouvons les déclarer finales en utilisant le mot-clé final. Pour, par exemple, final int a=10; final void display(){......} La méthode finale garantit que la fonctionnalité définie dans la superclasse ne sera jamais modifiée. 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 chose à laquelle je peux penser, c’est que le compilateur pourrait optimiser les variables finales et les incorporer dans le code en tant que constantes. Ainsi, vous ne vous retrouvez avec aucune mémoire allouée.

absolument, tant que la durée de vie de l’object est plus courte et que la gestion de la mémoire est très avantageuse, nous avons récemment examiné la fonctionnalité d’exportation avec des variables d’instance sur un test et une variable locale au niveau de la méthode. Lors des tests de charge, JVM émet une erreur de mémoire lors du premier test et JVM est arrêté. mais en deuxième test, réussi à obtenir le rapport en raison d’une meilleure gestion de la mémoire.

La seule fois où je préfère déclarer les variables locales comme définitives, c’est quand:

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

  • Je veux les rendre définitives (par exemple: une valeur qui ne devrait pas / ne peut pas être ignorée par erreur)

Est-ce qu’ils aident à la récupération rapide des ordures?
AFAIK un object devient un candidat de la collection GC s’il ne contient aucune référence forte et dans ce cas également, il n’y a aucune garantie qu’il sera immédiatement récupéré. En général, une référence forte est dite mourir si elle est hors de scope ou si l’utilisateur la réaffecte explicitement à une référence nulle. En les déclarant finales, cela signifie que la référence continuera d’exister jusqu’à ce que la méthode existe (à moins que sa scope ne soit explicitement réduite à un bloc interne spécifique {}) car vous ne pouvez pas réaffecter les variables finales. Je pense donc que la collecte des déchets pourrait entraîner un retard non souhaité.