Java Heap Space vs Stack – Asignación de memoria en Java

Hace algún tiempo escribí un par de publicaciones sobre Recolección de Basura en Java y Java es Pasado por Valor. Después de eso, recibí muchos correos electrónicos pidiendo explicaciones sobre Espacio de Montón de Java, Memoria de Pila de Java, Asignación de Memoria en Java y cuáles son las diferencias entre ellos. Verás muchas referencias a la memoria de montón y de pila en libros y tutoriales de Java, pero apenas una explicación completa de qué es la memoria de montón y de pila en términos de un programa.

Espacio de Montón de Java

El espacio de montón de Java es utilizado por el tiempo de ejecución de Java para asignar memoria a objetos y clases de JRE. Siempre que creamos un objeto, este se crea en el espacio de montón. La Recolección de Basura se ejecuta en la memoria de montón para liberar la memoria utilizada por objetos que no tienen ninguna referencia. Cualquier objeto creado en el espacio de montón tiene acceso global y puede ser referenciado desde cualquier parte de la aplicación.

Memoria de Pila de Java

La memoria de la pila de Java se utiliza para la ejecución de un hilo. Contiene valores específicos del método que son de corta duración y referencias a otros objetos en el montón que están siendo referidos desde el método. La memoria de la pila siempre se referencia en orden LIFO (Last-In-First-Out). Cada vez que se invoca un método, se crea un nuevo bloque en la memoria de la pila para que el método contenga valores primitivos locales y referencias a otros objetos en el método. Tan pronto como el método termina, el bloque queda sin usar y está disponible para el próximo método. El tamaño de la memoria de la pila es muy pequeño en comparación con la memoria del montón.

Montón y Memoria de la Pila en Programas Java

Entendamos el uso de la memoria del montón y de la pila con un programa sencillo.

package com.journaldev.test;

public class Memory {

	public static void main(String[] args) { // Line 1
		int i=1; // Line 2
		Object obj = new Object(); // Line 3
		Memory mem = new Memory(); // Line 4
		mem.foo(obj); // Line 5
	} // Line 9

	private void foo(Object param) { // Line 6
		String str = param.toString(); //// Line 7
		System.out.println(str);
	} // Line 8

}

La siguiente imagen muestra la memoria de la pila y del montón con respecto al programa anterior y cómo se utilizan para almacenar variables primitivas, objetos y variables de referencia. Vamos a seguir los pasos de la ejecución del programa.

  • Tan pronto como ejecutamos el programa, carga todas las clases de tiempo de ejecución en el espacio del montón. Cuando se encuentra el método main() en la línea 1, Java Runtime crea memoria de la pila que será utilizada por el hilo del método main().
  • Estamos creando una variable local primitiva en la línea 2, por lo que se crea y almacena en la memoria de la pila del método main().
  • Dado que estamos creando un objeto en la tercera línea, se crea en la memoria heap y la memoria de la pila contiene la referencia para él. Un proceso similar ocurre cuando creamos un objeto Memory en la cuarta línea.
  • Ahora, cuando llamamos al método foo() en la quinta línea, se crea un bloque en la parte superior de la pila para ser utilizado por el método foo(). Dado que Java pasa por valor, se crea una nueva referencia al objeto en el bloque de pila de foo() en la sexta línea.
  • A string is created in the 7th line, it goes in the String Pool in the heap space and a reference is created in the foo() stack space for it.
  • El método foo() termina en la octava línea, en este momento el bloque de memoria asignado para foo() en la pila se vuelve libre.
  • En la línea 9, el método main() termina y la memoria de la pila creada para el método main() se destruye. Además, el programa termina en esta línea, por lo tanto, Java Runtime libera toda la memoria y finaliza la ejecución del programa.

Diferencia entre el Espacio de Montón (Heap) de Java y la Memoria de Pila (Stack)

Basándonos en las explicaciones anteriores, podemos concluir fácilmente las siguientes diferencias entre la memoria heap y la memoria de la pila.

  1. La memoria heap es utilizada por todas las partes de la aplicación, mientras que la memoria de pila es utilizada solo por un hilo de ejecución.
  2. Siempre que se crea un objeto, se almacena en el espacio Heap y la memoria de la pila contiene la referencia a él. La memoria de la pila solo contiene variables primitivas locales y variables de referencia a objetos en el espacio Heap.
  3. Los objetos almacenados en el heap son accesibles globalmente, mientras que la memoria de la pila no puede ser accedida por otros hilos.
  4. La gestión de memoria en la pila se realiza de manera LIFO, mientras que es más compleja en la memoria Heap porque se utiliza globalmente. La memoria Heap se divide en Young-Generation, Old-Generation, etc., más detalles en Java Garbage Collection.
  5. La memoria de la pila tiene una vida corta, mientras que la memoria Heap vive desde el inicio hasta el final de la ejecución de la aplicación.
  6. Podemos usar la opción JVM -Xms y -Xmx para definir el tamaño de inicio y el tamaño máximo de la memoria Heap. Podemos usar -Xss para definir el tamaño de la memoria de la pila.
  7. Cuando la memoria de la pila está llena, la máquina virtual de Java lanza java.lang.StackOverFlowError, mientras que si la memoria Heap está llena, lanza un error java.lang.OutOfMemoryError: Java Heap Space.
  8. El tamaño de la memoria de la pila es muy pequeño en comparación con la memoria Heap. Debido a la simplicidad en la asignación de memoria (LIFO), la memoria de la pila es muy rápida en comparación con la memoria Heap.

Eso es todo para Java Heap Space vs Stack Memory en términos de aplicaciones Java, espero que aclare tus dudas con respecto a la asignación de memoria cuando se ejecuta cualquier programa Java.

Referencia: https://es.wikipedia.org/wiki/Modelo_de_memoria_de_Java.

Source:
https://www.digitalocean.com/community/tutorials/java-heap-space-vs-stack-memory