Memoria de pila y montón en Java

99

Según tengo entendido, en Java, la memoria de pila contiene primitivas e invocaciones de métodos y la memoria de montón se usa para almacenar objetos.

Supongamos que tengo una clase

class A {
       int a ;
       String b;
       //getters and setters
}
  1. ¿Dónde se almacenará el primitivo aen clase A?

  2. ¿Por qué existe la memoria de montón? ¿Por qué no podemos almacenar todo en la pila?

  3. Cuando el objeto se recolecta, ¿se destruye la pila asociada con el objeto?

Vinoth Kumar CM
fuente
1
stackoverflow.com/questions/1056444/is-it-on-the-stack-or-heap parece responder a su pregunta.
S.Lott
@ S.Lott, excepto que este es sobre Java, no C.
Péter Török
@ Péter Török: De acuerdo. Si bien el ejemplo de código es Java, no hay una etiqueta que indique que solo se trata de Java. Y el principio general debería aplicarse tanto a Java como a C. Además, hay muchas respuestas a esta pregunta sobre Stack Overflow.
S.Lott
99
@SteveHaigh: en este sitio, todo el mundo está demasiado preocupado sobre si algo pertenece aquí ... Me pregunto qué está compartiendo realmente este sitio con toda la curiosidad sobre si las preguntas pertenecen aquí o no.
Sam Goldberg

Respuestas:

106

La diferencia básica entre stack y heap es el ciclo de vida de los valores.

Los valores de pila solo existen dentro del alcance de la función en la que se crean. Una vez que regresa, se descartan.
Sin embargo, existen valores de montón en el montón. Se crean en algún momento y se destruyen en otro (ya sea por GC o manualmente, dependiendo del idioma / tiempo de ejecución).

Ahora Java solo almacena primitivas en la pila. Esto mantiene la pila pequeña y ayuda a mantener pequeños los marcos de pila individuales, permitiendo así más llamadas anidadas.
Los objetos se crean en el montón y solo las referencias (que a su vez son primitivas) se pasan en la pila.

Entonces, si crea un objeto, se coloca en el montón, con todas las variables que le pertenecen, para que pueda persistir después de que regrese la llamada a la función.

back2dos
fuente
2
"y solo referencias (que a su vez son primitivas)" ¿Por qué decir que las referencias son primitivas? ¿Puedes aclarar por favor?
Geek
55
@ Geek: Debido a que se aplica la definición común de tipos de datos primitivos: "un tipo de datos proporcionado por un lenguaje de programación como un bloque de construcción básico" . También puede notar que las referencias se enumeran entre los ejemplos canónicos más abajo en el artículo .
back2dos
44
@ Geek: en términos de datos, puede ver cualquiera de los tipos de datos primitivos, incluidas las referencias, como números. Incluso los chars son números y se pueden usar indistintamente como tal. Las referencias también son solo números que se refieren a una dirección de memoria, ya sea de 32 o 64 bits de largo (aunque no se pueden usar como tales, a menos que esté jugando sun.misc.Unsafe).
Sune Rasmussen
3
La terminología de esta respuesta es incorrecta. De acuerdo con la Especificación del lenguaje Java, las referencias NO son primitivas. Sin embargo, la esencia de lo que dice la respuesta es correcta. (Aunque se puede argumentar que las referencias son "en un sentido" primitivo es el de The JLS define la terminología para Java, y se dice que los tipos primitivos son. boolean, byte, short, char, int, long, floatY double.)
Stephen C
44
Como encontré en este artículo , Java puede almacenar objetos en la pila (o incluso en registros para pequeños objetos de corta duración). La JVM puede funcionar bastante bajo las sábanas. No es exactamente correcto decir "Ahora Java solo almacena primitivas en la pila".
49

¿Dónde se almacenan los campos primitivos?

Los campos primitivos se almacenan como parte del objeto que se instancia en alguna parte . La forma más fácil de pensar dónde está esto es el montón. Sin embargo , este no es siempre el caso. Como se describe en la teoría y práctica de Java: Leyendas del rendimiento urbano, revisado :

Las JVM pueden usar una técnica llamada análisis de escape, mediante la cual pueden decir que ciertos objetos permanecen confinados en un solo subproceso durante toda su vida útil, y que la vida útil está limitada por la vida útil de un marco de pila determinado. Dichos objetos se pueden asignar de forma segura en la pila en lugar del montón. Aún mejor, para objetos pequeños, la JVM puede optimizar la asignación por completo y simplemente elevar los campos del objeto en registros.

Por lo tanto, más allá de decir "el objeto se crea y el campo también está allí", no se puede decir si hay algo en el montón o en la pila. Tenga en cuenta que para los objetos pequeños y de corta duración, es posible que el 'objeto' no exista en la memoria como tal y en su lugar puede tener sus campos colocados directamente en los registros.

El artículo concluye con:

Las JVM son sorprendentemente buenas para descubrir cosas que solíamos suponer que solo el desarrollador podía saber. Al permitir que la JVM elija entre la asignación de la pila y la asignación del montón en una base de caso por caso, podemos obtener los beneficios de rendimiento de la asignación de la pila sin hacer que el programador agonice sobre si asignar en la pila o en el montón.

Por lo tanto, si tiene un código similar al siguiente:

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

donde ...no permite quxsalir de ese alcance, qux puede asignarse en la pila en su lugar. En realidad, esto es una victoria para la máquina virtual porque significa que no es necesario que se recolecte basura, desaparecerá cuando salga del alcance.

Más sobre análisis de escape en Wikipedia. Para aquellos dispuestos a profundizar en los documentos, Escape Analysis for Java de IBM. Para aquellos que vienen de un mundo de C #, pueden encontrar buenas lecturas de The Stack Is An Implementation Detail y The Truth About Value de Eric Lippert (son útiles para los tipos de Java, ya que muchos de los conceptos y aspectos son iguales o similares) . ¿Por qué los libros .Net hablan de la asignación de memoria de pila frente a pila? También entra en esto.

En los porqués de la pila y el montón

En el montón

Entonces, ¿por qué tener la pila o el montón? Para las cosas que dejan alcance, la pila puede ser costosa. Considera el código:

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

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

void qux(String arg) {
    ...
}

Los parámetros también son parte de la pila. En el caso de que no tenga un montón, estaría pasando el conjunto completo de valores en la pila. Esto está bien para "foo"cadenas pequeñas ... pero qué pasaría si alguien colocara un archivo XML enorme en esa cadena. Cada llamada copiaría toda la cadena enorme en la pila, y eso sería un desperdicio.

En cambio, es mejor colocar los objetos que tienen algo de vida fuera del alcance inmediato (pasados ​​a otro alcance, atrapados en una estructura que alguien más está manteniendo, etc.) en otra área que se llama el montón.

En la pila

No necesitas la pila. Uno podría, hipotéticamente, escribir un lenguaje que no use una pila (de profundidad arbitraria). Un viejo BASIC que aprendí en mi juventud lo hizo, uno solo podía hacer 8 niveles de gosubllamadas y todas las variables eran globales, no había pila.

La ventaja de la pila es que cuando tiene una variable que existe con un ámbito, cuando abandona ese ámbito, se abre el marco de la pila. Realmente simplifica lo que hay y lo que no. El programa pasa a otro procedimiento, un nuevo marco de pila; el programa vuelve al procedimiento y usted vuelve al que ve su alcance actual; el programa deja el procedimiento y todos los elementos de la pila se desasignan.

Esto realmente facilita la vida de la persona que escribe el tiempo de ejecución para que el código use una pila y un montón. Simplemente tienen muchos conceptos y formas de trabajar en el código, lo que permite a la persona que escribe el código en el idioma liberarse de pensar explícitamente en ellos.

La naturaleza de la pila también significa que no puede fragmentarse. La fragmentación de la memoria es un problema real con el montón. Asigne algunos objetos, luego la basura recolecte uno del medio y luego intente encontrar espacio para asignar el siguiente grande. Es un desastre. Ser capaz de poner cosas en la pila significa que no tienes que lidiar con eso.

Cuando algo es basura recolectada

Cuando algo es basura recolectada, se va. Pero es solo basura recolectada porque ya se ha olvidado: no hay más referencias al objeto en el programa a las que se pueda acceder desde el estado actual del programa.

Señalaré que esta es una simplificación muy grande de la recolección de basura. Hay muchos recolectores de basura (incluso dentro de Java; puede ajustar el recolector de basura mediante el uso de varias banderas ( documentos ). Estos se comportan de manera diferente y los matices de cómo cada uno hace las cosas son demasiado profundos para esta respuesta. Es posible que desee leer Conceptos básicos de Java Garbage Collection para tener una mejor idea de cómo funciona algo de eso.

Dicho esto, si algo se asigna en la pila, no es basura recolectada como parte de System.gc()él, se desasigna cuando aparece el marco de la pila. Si hay algo en el montón y se hace referencia desde algo en la pila, no se recolectará basura en ese momento.

¿Por qué importa esto?

En su mayor parte, su tradición. Los libros de texto escritos y las clases de compilación y la documentación de varios bits hacen un gran negocio sobre el montón y la pila.

Sin embargo, las máquinas virtuales de hoy (JVM y similares) han hecho todo lo posible para tratar de mantener esto oculto al programador. A menos que se esté quedando sin uno u otro y necesite saber por qué (en lugar de simplemente aumentar el espacio de almacenamiento de manera adecuada), no importa demasiado.

El objeto está en algún lugar y está en el lugar donde se puede acceder de forma correcta y rápida durante el tiempo adecuado que existe. Si está en la pila o en el montón, realmente no importa.

Comunidad
fuente
7
  1. En el montón, como parte del objeto, al que hace referencia un puntero en la pila. es decir. ayb se almacenarán adyacentes entre sí.
  2. Porque si toda la memoria fuera memoria de pila, ya no sería eficiente. Es bueno tener un área pequeña de acceso rápido donde comenzamos y tener esos elementos de referencia en el área de memoria mucho más grande que queda. Sin embargo, esto es excesivo cuando un objeto es simplemente una primitiva única que ocuparía aproximadamente la misma cantidad de espacio en la pila que el puntero a él.
  3. Si.
pdr
fuente
1
Agregaría a su punto # 2 que si almacenó objetos en la pila (imagine un objeto de diccionario con cientos de miles de entradas), para pasarlo o devolverlo desde una función necesitaría copiar el objeto cada hora. Al usar un puntero o referencia a un objeto en el montón, solo pasamos la referencia (pequeña).
Scott Whitlock
1
Pensé que 3 sería 'no' porque si el objeto se está recolectando basura, entonces no hay ninguna referencia en la pila que lo señale.
Luciano
@Luciano - Veo tu punto. Leí la pregunta 3 de manera diferente. El "al mismo tiempo" o "para ese momento" está implícito. :: encogimiento de hombros ::
pdr
3
  1. En el montón, a menos que Java asigne la instancia de clase en la pila como una optimización después de probar mediante análisis de escape que esto no afectará la semántica. Sin embargo, este es un detalle de implementación, por lo que para todos los fines prácticos, excepto la microoptimización, la respuesta está "en el montón".

  2. La memoria de la pila debe asignarse y desasignarse en el último orden de salida. La memoria de montón se puede asignar y desasignar en cualquier orden.

  3. Cuando el objeto es basura recolectada, no hay más referencias apuntando a él desde la pila. Si lo hubiera, mantendrían vivo el objeto. Las primitivas de pila no son basura recolectada porque se destruyen automáticamente cuando la función regresa.

dsimcha
fuente
3

La memoria de pila se usa para almacenar variables locales y llamadas a funciones.

Mientras que la memoria de almacenamiento dinámico se usa para almacenar objetos en Java. No importa, dónde se crea el objeto en el código.

Cuando se lo primitivo ade class Aser almacenado?

En este caso, la primitiva a está asociada con un objeto de clase A. Por lo tanto, se crea en la memoria del montón.

¿Por qué existe la memoria de montón? ¿Por qué no podemos almacenar todo en la pila?

  • Las variables creadas en la pila quedarán fuera de alcance y se destruirán automáticamente.
  • La pila es mucho más rápida de asignar en comparación con las variables en el montón.
  • El recolector de basura debe destruir las variables del montón.
  • Más lento para asignar en comparación con las variables en la pila.
  • Usaría la pila si sabe exactamente cuántos datos necesita asignar antes del tiempo de compilación y no es demasiado grande (las variables locales primitivas se almacenan en la pila)
  • Usaría el montón si no sabe exactamente cuántos datos necesitará en tiempo de ejecución o si necesita asignar una gran cantidad de datos.

Cuando el objeto se recolecta, ¿se destruye la pila asociada con el objeto?

Garbage Collector funciona bajo el alcance de la memoria del montón, por lo que destruye los objetos que no tienen una cadena de referencia desde la raíz.

Premraj
fuente
-1

Para una mejor comprensión (memoria de montón / pila):

Imagen de memoria de pila / pila

Yousha Aleayoub
fuente