Où vit le pool de constantes String de Java, le tas ou la pile?

104

Je connais le concept d'un pool de constantes et le pool de constantes String utilisé par les JVM pour gérer les littéraux String. Mais je ne sais pas quel type de mémoire est utilisé par la JVM pour stocker les littéraux constants String. La pile ou le tas? Puisqu'il s'agit d'un littéral qui n'est associé à aucune instance, je suppose qu'il sera stocké dans la pile. Mais s'il n'est référencé par aucune instance, le littéral doit être collecté par GC run (corrigez-moi si je me trompe), alors comment cela est-il géré s'il est stocké dans la pile?

Rengasami Ramanujam
la source
11
Comment un pool peut-il être stocké sur la pile? connaissez-vous le concept de pile?
The Scrum Meister
1
Salut Scrum Meister, j'ai essayé de dire que ça ne pouvait pas être. Désolé pour la mauvaise convention. Concernant GC Je viens de le savoir. Merci pour cela
Rengasami Ramanujam
@TheScrumMeister - en fait, dans certaines circonstances, ils peuvent être ramassés. Le "deal breaker" est que l'objet de code pour toute classe qui mentionne un littéral de chaîne aura une référence à l'objet String qui représente le littéral.
Stephen C

Réponses:

74

La réponse n'est techniquement ni l'un ni l'autre. Selon la spécification de la machine virtuelle Java, la zone de stockage des littéraux de chaîne se trouve dans le pool de constantes d'exécution . La zone de mémoire du pool de constantes d'exécution est allouée par classe ou par interface, elle n'est donc pas du tout liée à des instances d'objet. Le pool de constantes d'exécution est un sous-ensemble de la zone de méthode qui qui "stocke les structures par classe telles que le pool de constantes d'exécution, les données de champ et de méthode, et le code des méthodes et des constructeurs, y compris les méthodes spéciales utilisées dans l'initialisation et l'interface de classe et d'instance. initialisation de type ". La spécification VM indique que bien que la zone de méthode fait logiquement partie du tas, il ne dicte pas que la mémoire allouée dans la zone de méthode soit soumise au garbage collection ou à d'autres comportements qui seraient associés aux structures de données normales allouées au tas .

Duane Moore
la source
8
En fait, lorsque les classes sont chargées dans la VM, les constantes de chaîne seront copiées dans le tas, dans un pool de chaînes à l'échelle de la VM (dans le permgen, comme l'a dit Stephen C), car les littéraux de chaîne égaux dans différentes classes doivent être le même objet String (par le JLS).
Paŭlo Ebermann
1
Merci à tous pour vos réponses. J'ai beaucoup compris avec cette discussion. C'est bon de vous connaître :)
Rengasami Ramanujam
4
Paŭlo, c'est vrai pour la machine virtuelle de Sun, mais pas nécessairement pour toutes les implémentations de la JVM. Comme l'indique la spécification JVM, bien que le pool de constantes d'exécution et la zone de méthode fassent logiquement partie du tas, ils n'ont pas besoin d'avoir le même comportement. Juste une différence sémantique mineure, vraiment :)
Duane Moore
54

Comme expliqué par cette réponse , l'emplacement exact du pool de chaînes n'est pas spécifié et peut varier d'une implémentation JVM à une autre.

Il est intéressant de noter que jusqu'à Java 7, le pool se trouvait dans l'espace permgen du tas sur hotspot JVM mais il a été déplacé vers la partie principale du tas depuis Java 7 :

Zone : HotSpot
Synopsis : Dans JDK 7, les chaînes internées ne sont plus allouées dans la génération permanente du tas Java, mais sont à la place allouées dans la partie principale du tas Java (connue sous le nom de jeunes et anciennes générations), avec l'autre objets créés par l'application. Cette modification entraînera davantage de données résidant dans le tas Java principal et moins de données dans la génération permanente, et peut donc nécessiter l'ajustement des tailles de tas. La plupart des applications ne verront que des différences relativement petites dans l'utilisation du tas en raison de ce changement, mais les applications plus volumineuses qui chargent de nombreuses classes ou font un usage intensif de la méthode String.intern () verront des différences plus importantes. RFE: 6962931

Et dans Java 8 Hotspot, la génération permanente a été complètement supprimée.

assylies
la source
30

Les littéraux de chaîne ne sont pas stockés sur la pile. Jamais. En fait, aucun objet n'est stocké sur la pile.

Les littéraux de chaîne (ou plus précisément les objets String qui les représentent) sont historiquement stockés dans un tas appelé le tas "permgen". (Permgen est l'abréviation de génération permanente.)

Dans des circonstances normales, les littéraux de chaîne et la plupart des autres éléments du tas permgen sont accessibles "en permanence" et ne sont pas récupérés. (Par exemple, les littéraux de chaîne sont toujours accessibles à partir des objets de code qui les utilisent.) Cependant, vous pouvez configurer une machine virtuelle Java pour tenter de trouver et de collecter des classes chargées dynamiquement qui ne sont plus nécessaires, ce qui peut entraîner la récupération des déchets de littéraux de chaîne. .

CLARIFICATION # 1 - Je ne dis pas que Permgen n'obtient pas GC. C'est le cas, généralement lorsque la JVM décide d'exécuter un GC complet. Mon point est que les littéraux de chaîne seront accessibles tant que le code qui les utilise est accessible, et le code sera accessible tant que le chargeur de classe du code est accessible, et pour les chargeurs de classe par défaut, cela signifie "pour toujours".

CLARIFICATION # 2 - En fait, Java 7 et les versions ultérieures utilisent le tas normal pour contenir le pool de chaînes. Ainsi, les objets String qui représentent les littéraux String et les chaînes internes sont en fait dans le tas normal. (Voir la réponse de @ assylias pour plus de détails.)


Mais j'essaie toujours de découvrir la ligne mince entre le stockage de la chaîne littérale et la chaîne créée avec new.

Il n'y a pas de «ligne fine». C'est vraiment très simple:

  • String les objets qui représentent / correspondent à des chaînes littérales sont conservés dans le pool de chaînes.
  • Stringles objets créés par un String::internappel sont conservés dans le pool de chaînes.
  • Tous les autres Stringobjets ne sont PAS contenus dans le pool de chaînes.

Ensuite, il y a la question distincte de savoir où le pool de chaînes est "stocké". Avant Java 7, c'était le tas permgen. À partir de Java 7, c'est le tas principal.

Stephen C
la source
23

Regroupement de chaînes

Le regroupement de chaînes (parfois également appelé canonisation de chaînes) est un processus de remplacement de plusieurs objets String avec une valeur égale mais une identité différente par un seul objet String partagé. Vous pouvez atteindre cet objectif en conservant votre propre carte (avec des références éventuellement souples ou faibles selon vos besoins) et en utilisant des valeurs de carte comme valeurs canonisées. Ou vous pouvez utiliser la méthode String.intern () qui vous est fournie par JDK.

À l'époque de Java 6, l'utilisation de String.intern () était interdite par de nombreuses normes en raison d'une forte possibilité d'obtenir une exception OutOfMemoryException si le regroupement devenait incontrôlable. L'implémentation Oracle Java 7 du regroupement de chaînes a été considérablement modifiée. Vous pouvez rechercher des détails dans http://bugs.sun.com/view_bug.do?bug_id=6962931 et http://bugs.sun.com/view_bug.do?bug_id=6962930 .

String.intern () dans Java 6

À l'époque, toutes les chaînes internées étaient stockées dans PermGen - la partie de taille fixe du tas principalement utilisée pour stocker les classes chargées et le pool de chaînes. Outre les chaînes explicitement internées, le pool de chaînes PermGen contenait également toutes les chaînes littérales précédemment utilisées dans votre programme (le mot important ici est utilisé - si une classe ou une méthode n'a jamais été chargée / appelée, les constantes qui y sont définies ne seront pas chargées).

Le plus gros problème avec un tel pool de chaînes dans Java 6 était son emplacement - le PermGen. PermGen a une taille fixe et ne peut pas être étendu au moment de l'exécution. Vous pouvez le définir en utilisant l'option -XX: MaxPermSize = 96m. Autant que je sache, la taille par défaut de PermGen varie entre 32M et 96M selon la plate-forme. Vous pouvez augmenter sa taille, mais sa taille sera toujours fixe. Une telle limitation nécessitait une utilisation très prudente de String.intern - vous feriez mieux de ne pas interner aucune entrée utilisateur incontrôlée en utilisant cette méthode. C'est pourquoi le regroupement de chaînes à l'époque de Java 6 était principalement implémenté dans les cartes gérées manuellement.

String.intern () dans Java 7

Les ingénieurs d'Oracle ont apporté une modification extrêmement importante à la logique de regroupement de chaînes dans Java 7: le pool de chaînes a été déplacé vers le tas. Cela signifie que vous n'êtes plus limité par une zone de mémoire de taille fixe distincte. Toutes les chaînes sont désormais situées dans le tas, comme la plupart des autres objets ordinaires, ce qui vous permet de gérer uniquement la taille du tas lors du réglage de votre application. Techniquement, cela seul pourrait être une raison suffisante pour reconsidérer l'utilisation de String.intern () dans vos programmes Java 7. Mais il y a d'autres raisons.

Les valeurs du pool de chaînes sont récupérées

Oui, toutes les chaînes du pool de chaînes JVM sont éligibles pour le garbage collection s'il n'y a aucune référence à elles à partir des racines de votre programme. Il s'applique à toutes les versions discutées de Java. Cela signifie que si votre chaîne internée est hors de portée et qu'il n'y a pas d'autres références à celle-ci, elle sera récupérée à partir du pool de chaînes JVM.

Étant éligible pour le garbage collection et résidant dans le tas, un pool de chaînes JVM semble être un bon endroit pour toutes vos chaînes, n'est-ce pas? En théorie, c'est vrai - les chaînes non utilisées seront récupérées dans le pool, les chaînes utilisées vous permettront d'économiser de la mémoire au cas où vous obtiendriez une chaîne égale à partir de l'entrée. Semble être une stratégie d'économie de mémoire parfaite? Presque oui. Vous devez savoir comment le pool de chaînes est implémenté avant de prendre des décisions.

la source.

En essayant
la source
11

Comme d'autres réponses l'expliquent, la mémoire en Java est divisée en deux parties

1. Pile: Une pile est créée par thread et elle stocke des cadres de pile qui stockent à nouveau des variables locales et si une variable est un type de référence alors cette variable fait référence à un emplacement de mémoire dans le tas pour l'objet réel.

2. Heap: tous les types d'objets seront créés uniquement dans le tas.

La mémoire du tas est à nouveau divisée en 3 parties

1. Young Generation: Stocke les objets qui ont une durée de vie courte, Young Generation elle-même peut être divisée en deux catégories Eden Space et Survivor Space .

2. Ancienne génération: stockez les objets qui ont survécu à de nombreux cycles de garbage collection et qui sont toujours référencés.

3. Génération permanente: stocke les métadonnées sur le programme, par exemple le pool de constantes d'exécution.

Le pool de constantes de chaîne appartient à la zone de génération permanente de la mémoire du tas.

Nous pouvons voir le pool de constantes d'exécution pour notre code dans le bytecode en utilisant javap -verbose class_namece qui nous montrera les références de méthode (#Methodref), les objets de classe (#Class), les littéraux de chaîne (#String)

pool de constantes d'exécution

Vous pouvez en savoir plus à ce sujet dans mon article Comment la JVM gère-t-elle la surcharge et le remplacement de méthode en interne .

Naresh Joshi
la source
Veuillez divulguer toute affiliation et ne pas utiliser le site comme un moyen de promouvoir votre site par la publication. Voir Comment rédiger une bonne réponse? .
9

Aux bonnes réponses déjà incluses ici, je veux ajouter quelque chose qui manque à mon point de vue - une illustration.

Comme vous l'avez déjà fait, JVM divise la mémoire allouée à un programme Java en deux parties. un est pile et un autre est tas . La pile est utilisée à des fins d'exécution et le tas est utilisé à des fins de stockage. Dans cette mémoire de tas, JVM alloue de la mémoire spécialement conçue pour les littéraux de chaîne. Cette partie de la mémoire du tas est appelée pool de constantes de chaîne .

Ainsi, par exemple, si vous initiez les objets suivants:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

Les littéraux de chaîne s1et s2iront dans le pool de constantes de chaîne, les objets obj1, obj2, obj3 dans le tas. Tous seront référencés à partir de la pile.

Notez également que "abc" apparaîtra dans le tas et dans le pool de constantes de chaîne. Pourquoi est-ce String s1 = "abc"et String obj1 = new String("abc")sera-t-il créé de cette façon? C'est parce que String obj1 = new String("abc")crée explicitement une instance nouvelle et référentiellement distincte d'un objet String et String s1 = "abc"peut réutiliser une instance du pool de constantes de chaîne si elle est disponible. Pour une explication plus élaborée: https://stackoverflow.com/a/3298542/2811258

entrez la description de l'image ici

Johnny
la source
Dans le diagramme donné, où existeraient les littéraux "def" et "456". Et comment seraient-ils référencés?
Satyendra du
Merci pour votre commentaire @Satyendra, j'ai mis à jour l'illustration et la réponse.
Johnny
@Stas pourquoi un autre objet String "abc" est créé..il doit utiliser la référence obj1 pour pointer le littéral à droite?
C'est parce que String obj1 = new String ("abc") crée explicitement une nouvelle instance référentiellement distincte d'un objet String et String s1 = "abc" peut réutiliser une instance du pool de constantes de chaîne si elle est disponible. Pour une explication plus élaborée: stackoverflow.com/a/3298542/2811258
Johnny