web-dev-qa-db-fra.com

Stack and Heap memory in Java

Si je comprends bien, en Java, la mémoire de pile contient des primitives et des invocations de méthode et la mémoire de tas est utilisée pour stocker des objets.

Supposons que j'ai une classe

class A {
       int a ;
       String b;
       //getters and setters
}
  1. Où la primitive a de la classe A sera-t-elle stockée?

  2. Pourquoi la mémoire de tas existe-t-elle? Pourquoi ne pouvons-nous pas tout stocker sur la pile?

  3. Lorsque l'objet est récupéré, la pile associée à l'objet est-elle détruite?

101
Vinoth Kumar C M

La différence fondamentale entre la pile et le tas est le cycle de vie des valeurs.

Les valeurs de pile n'existent que dans la portée de la fonction dans laquelle elles sont créées. Une fois qu'elle revient, elles sont supprimées.
Des valeurs de tas existent cependant sur le tas. Ils sont créés à un moment donné et détruits à un autre (soit par GC, soit manuellement, selon la langue/le runtime).

Maintenant Java ne stocke que les primitives sur la pile. Cela maintient la pile petite et aide à garder les trames de pile individuelles petites, permettant ainsi plus d'appels imbriqués.
Les objets sont créés sur le tas, et seules les références (qui à leur tour sont des primitives) sont transmises sur la pile.

Donc, si vous créez un objet, il est placé sur le tas, avec toutes les variables qui lui appartiennent, afin qu'il puisse persister après le retour de l'appel de fonction.

106
back2dos

Où sont stockés les champs primitifs?

Les champs primitifs sont stockés dans le cadre de l'objet qui est instancié quelque part . La façon la plus simple de savoir où cela se trouve - c'est le tas. Cependant , ce n'est pas toujours le cas. Comme décrit dans Théorie et pratique Java: légendes de la performance urbaine, revisitées :

Les machines virtuelles Java peuvent utiliser une technique appelée analyse d'échappement, par laquelle elles peuvent dire que certains objets restent confinés à un seul thread pendant toute leur durée de vie et que cette durée de vie est limitée par la durée de vie d'une trame de pile donnée. Ces objets peuvent être alloués en toute sécurité sur la pile au lieu du tas. Encore mieux, pour les petits objets, la JVM peut optimiser entièrement l'allocation et simplement hisser les champs de l'objet dans des registres.

Ainsi, au-delà de dire "l'objet est créé et le champ est là aussi", on ne peut pas dire si quelque chose est sur le tas ou sur la pile. Notez que pour les petits objets à courte durée de vie, il est possible que "l'objet" n'existe pas en mémoire en tant que tel et que ses champs soient placés directement dans des registres.

Le document se termine par:

Les machines virtuelles Java sont étonnamment bonnes pour comprendre des choses que nous supposions que seul le développeur pouvait savoir. En laissant la JVM choisir entre l'allocation de pile et l'allocation de tas au cas par cas, nous pouvons obtenir les avantages de performance de l'allocation de pile sans que le programmeur ne se soucie de l'allocation sur la pile ou sur le tas.

Ainsi, si vous avez du code qui ressemble à:

void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}

... ne permet pas à qux de quitter cette portée, qux peut être alloué sur la pile à la place. Il s'agit en fait d'une victoire pour le VM car cela signifie qu'il n'a jamais besoin d'être récupéré - il disparaîtra lorsqu'il quittera la portée.

Plus sur analyse d'échappement sur Wikipedia. Pour ceux qui souhaitent se plonger dans les articles, Escape Analysis for Java d'IBM. Pour ceux qui viennent d'un monde C #, vous pouvez trouver La pile est un détail d'implémentation et La vérité sur les types de valeur par Eric Lippert bonnes lectures (elles sont utiles pour Java types également car de nombreux concepts et aspects sont identiques ou similaires). Pourquoi les livres .Net parlent-ils de l'allocation de mémoire pile vs tas? y va également.

Sur le pourquoi de la pile et du tas

Sur le tas

Alors, pourquoi avoir la pile ou le tas? Pour les choses qui quittent la portée, la pile peut être coûteuse. Considérez le code:

void foo(String arg) {
    bar(arg);
    ...
}

void bar(String arg) {
    qux(arg);
    ...
}

void qux(String arg) {
    ...
}

Les paramètres font également partie de la pile. Dans le cas où vous ne disposez pas d'un tas, vous passeriez l'ensemble complet de valeurs sur la pile. C'est très bien pour "foo" Et les petites chaînes ... mais que se passerait-il si quelqu'un mettait un énorme fichier XML dans cette chaîne. Chaque appel copiera la chaîne énorme entière sur la pile - et que serait tout à fait inutile.

Au lieu de cela, il est préférable de placer les objets qui ont une certaine vie en dehors de la portée immédiate (passés à une autre portée, coincés dans une structure que quelqu'un d'autre maintient, etc ...) dans une autre zone appelée le tas.

Sur la pile

Vous n'avez pas besoin de la pile. On pourrait, hypothétiquement, écrire un langage qui n'utilise pas de pile (de profondeur arbitraire). Un vieux BASIC que j'ai appris dans ma jeunesse le faisait, on ne pouvait faire que 8 niveaux d'appels gosub et toutes les variables étaient globales - il n'y avait pas de pile.

L'avantage de la pile est que lorsque vous avez une variable qui existe avec une étendue, lorsque vous quittez cette étendue, ce cadre de pile est sauté. Cela simplifie vraiment ce qui est là et ce qui n'est pas là. Le programme passe à une autre procédure, un nouveau cadre de pile; le programme revient à la procédure, et vous avez de retour dans celui qui voit votre portée actuelle; le programme quitte la procédure et tous les éléments de la pile sont désalloués.

Cela facilite vraiment la vie de la personne qui écrit le runtime pour que le code utilise une pile et un tas. Ils ont simplement de nombreux concepts et façons de travailler sur le code permettant à la personne qui écrit le code dans la langue d'être libéré de penser explicitement à eux.

La nature de la pile signifie également qu'elle ne peut pas être fragmentée. Fragmentation de la mémoire est un vrai problème avec le tas. Vous allouez quelques objets, puis récupérez un objet du milieu, puis essayez de trouver de l'espace pour le prochain grand à allouer. C'est le bordel. Être capable de mettre des choses sur la pile à la place signifie que vous n'avez pas à vous en occuper.

Quand quelque chose est ramassé

Quand quelque chose est ramassé, c'est parti. Mais ce ne sont que des ordures collectées car elles sont déjà oubliées - il n'y a plus de références à l'objet dans le programme accessibles à partir de l'état actuel du programme.

Je soulignerai qu'il s'agit d'une très grande simplification de la collecte des ordures. Il existe de nombreux ramasse-miettes (même au sein de Java - vous pouvez modifier le ramasse-miettes en utilisant divers indicateurs ( docs ). Ceux-ci se comportent différemment et les nuances de la façon dont chacun fait les choses est un peu trop profond pour cette réponse. Vous voudrez peut-être lire Java Garbage Collection Basics pour avoir une meilleure idée de comment cela fonctionne.

Cela dit, si quelque chose est alloué sur la pile, il n'est pas récupéré dans le cadre de System.gc() - il est désalloué lorsque le cadre de la pile apparaît. Si quelque chose est sur le tas et référencé à partir de quelque chose sur la pile, il ne sera pas récupéré à ce moment-là.

Pourquoi est-ce important?

Pour l'essentiel, sa tradition. Les livres de texte écrits et les classes de compilateur et la documentation des différents bits font beaucoup pour le tas et la pile.

Cependant, les machines virtuelles d'aujourd'hui (JVM et similaires) se sont donné beaucoup de mal pour essayer de cacher cela au programmeur. À moins que vous ne manquiez de l'un ou de l'autre et que vous ayez besoin de savoir pourquoi (plutôt que d'augmenter simplement l'espace de stockage de manière appropriée), cela n'a pas trop d'importance beaucoup.

L'objet est quelque part et son à l'endroit où il peut être accédé correctement et rapidement pendant la durée appropriée qu'il existe. Si c'est sur la pile ou sur le tas - cela n'a pas vraiment d'importance.

49
user40980
  1. Dans le tas, en tant que partie de l'objet, qui est référencée par un pointeur dans la pile. c'est à dire. a et b seront stockés l'un à côté de l'autre.
  2. Parce que si toute la mémoire était de la mémoire de pile, elle ne serait plus efficace. Il est bon d'avoir une petite zone d'accès rapide où nous commençons et d'avoir ces éléments de référence dans la zone de mémoire beaucoup plus grande qui reste. Cependant, c'est exagéré quand un objet est simplement une primitive unique qui prendrait à peu près la même quantité d'espace sur la pile que le pointeur vers elle le ferait.
  3. Oui.
7
pdr
  1. Sur le tas, sauf si Java alloue l'instance de classe sur la pile en tant qu'optimisation après avoir prouvé via l'analyse d'échappement que cela n'affectera pas la sémantique. Ceci est un détail d'implémentation, cependant, donc à toutes fins pratiques sauf micro-optimisation la réponse est "sur le tas".

  2. La mémoire de la pile doit être allouée et désallouée en dernier dans le premier ordre de sortie. La mémoire de tas peut être allouée et désallouée dans n'importe quel ordre.

  3. Lorsque l'objet est récupéré, il n'y a plus de références pointant vers lui depuis la pile. S'il y en avait, ils garderaient l'objet en vie. Les primitives de pile ne sont pas du tout récupérées car elles sont automatiquement détruites lorsque la fonction revient.

3
dsimcha

La mémoire de pile est utilisée pour stocker les variables locales et l'appel de fonction.

Alors que la mémoire de tas est utilisée pour stocker des objets en Java. Peu importe, où l'objet est créé dans le code.

Où sera la primitive a dans class A être stocké?

Dans ce cas, la primitive a est associée à un objet de classe A. Il crée donc dans la mémoire de tas.

Pourquoi la mémoire de tas existe-t-elle? Pourquoi ne pouvons-nous pas tout stocker sur la pile?

  • Les variables créées sur la pile seront hors de portée et automatiquement détruites.
  • La pile est beaucoup plus rapide à allouer par rapport aux variables du tas.
  • Les variables du tas doivent être détruites par Garbage Collector.
  • Allocation plus lente par rapport aux variables de la pile.
  • Vous utiliseriez la pile si vous savez exactement combien de données vous devez allouer avant la compilation et qu'elle n'est pas trop grande (les variables locales primitives stockent dans la pile)
  • Vous utiliseriez le tas si vous ne savez pas exactement combien de données vous aurez besoin au moment de l'exécution ou si vous devez allouer beaucoup de données.

Lorsque l'objet est récupéré, la pile associée à l'objet est-elle détruite?

Garbage Collector fonctionne dans le cadre de la mémoire du tas, donc il détruit les objets qui n'ont pas de chaîne de référence de la racine.

3
Premraj