Java Stack vs Heap: Guía práctica de asignación de memoria
¿Qué es la memoria Stack?
En Java, la pila (stack) es una región de memoria contigua que contiene marcos de llamadas a métodos, variables locales y variables de referencia. Cada vez que se invoca un método, se apila un nuevo marco en la pila. La memoria de pila se organiza en modo Last-In, First-Out (LIFO), lo que significa que la llamada más reciente es la primera en eliminarse cuando el método retorna.
¿Qué es la memoria Heap?
El heap es el área donde se crean los objetos Java. Contiene todas las instancias de clases y, opcionalmente, variables de referencia que apuntan a esos objetos. A diferencia de la pila, el heap no está ligado al ciclo de vida de una sola llamada a método, permitiendo que los objetos persistan más allá del ámbito en el que fueron creados.
Asignación de memoria en Java
La asignación de memoria en Java se refiere al proceso de reservar memoria virtual para los datos del programa. Las variables no se asignan en la declaración; en su lugar, se crea una referencia. Cuando se usa el operador new, la JVM asigna espacio en el heap para el objeto.
Java divide la memoria en cuatro secciones distintas para una gestión eficiente:
- Heap
- Stack
- Código (Bytecode)
- Estático (Datos de clase)
- La sección Código contiene el bytecode compilado.
- La Pila almacena marcos de métodos, variables locales y variables de referencia.
- El Heap guarda objetos y, ocasionalmente, variables de referencia.
- La sección Estática mantiene campos y métodos estáticos.
Variables locales vs. variables de instancia
Las variables de instancia se declaran dentro de una clase pero fuera de cualquier método:
class Estudiante {
int num; // variable de instancia
public void showData() {}
}
Las variables locales se declaran dentro de un método (o como parámetros de método):
public void suma(int a) {
int x = a + 3; // a y x son variables locales
}
Stack vs Heap en acción
Recorramos un ejemplo para ilustrar cómo interactúan la pila y el heap durante las llamadas a métodos.
Supongamos que el método main llama a m1():
public void m1() {
int x = 20;
}
Se apila un marco para m1 en la pila. La variable local x se almacena en este marco.
Cuando m1 llama a m2(int b), se añade un nuevo marco encima del de m1:
public void m2(int b) {
boolean c;
}
De forma similar, m2 llama a m3(), creando otro marco:
Dentro de m3, se instancia un objeto Cuenta en el heap, mientras que su referencia ref vive en la pila:
class Cuenta {
int p;
int q;
}
public void m3() {
Cuenta ref = new Cuenta(); // código adicional
}
La variable de referencia ref se crea en el marco de pila de m3 y apunta al objeto Cuenta asignado en el heap.
Cuando m3 finaliza, su marco de pila se desapila, dejando el objeto Cuenta sin referencias. Se vuelve elegible para recolección de basura.
El mismo proceso se repite para m2 y m1, retornando finalmente el control a main.
Referencias a objetos como variables de instancia
Considera una clase A que contiene una referencia a una instancia de B:
public static void main(String[] args) {
A padre = new A(); // código adicional
}
class A {
B hijo = new B();
int e;
}
class B {
int c;
int d;
}
Aquí, la referencia hijo se almacena en el heap como parte del objeto A, apuntando a su propia instancia B.
Conclusiones clave
- Cada llamada a método crea un nuevo marco en la pila.
- Las variables locales residen en la pila; las variables de instancia están en el heap.
- Las variables de referencia se almacenan en la pila pero apuntan a objetos en el heap.
- Cuando un marco de pila se desapila, sus variables locales y de referencia se descartan, y los objetos huérfanos en el heap se vuelven elegibles para recolección de basura.
Java
- Variables y Literales en Java: Guía Completa
- Clases y Objetos en Java: Guía Práctica
- Clases y métodos abstractos en Java: Guía completa
- Dominando clases anidadas e internas en Java: Tipos, ejemplos y mejores prácticas
- Java throw vs throws: Dominando el manejo de excepciones con ejemplos prácticos
- Tipos de anotaciones en Java: Guía completa de predefinidas, personalizadas y meta‑anotaciones
- Dominando Autoboxing y Unboxing en Java: Ejemplos Prácticos y Mejores Prácticas
- Dominando el reemplazo de Strings en Java: replace(), replaceAll() y replaceFirst() explicados
- Sobrescritura de Métodos en Java: Personalizando el Comportamiento de la Superclase
- Mejoras en Java 10: Asignar Heap en NV-DIMM con -XX:AllocateHeapAt
- Dominar Referencias a Métodos en Java 8: Guía Completa