Gestión de memoria en Java

⚡ Resumen inteligente

Gestión de memoria en Java explica cómo la JVM divide la memoria de tiempo de ejecución entre Stack, Heap, Codey las regiones estáticas, cómo fluyen las referencias a objetos durante las llamadas a métodos y cómo el recolector de basura recupera los objetos inaccesibles para que las aplicaciones se mantengan estables y libres de fugas de memoria.

  • 🧠 Diseño de la memoria de la JVM: Java La memoria se divide en un montón (Heap) para objetos, una pila (Stick) para marcos de métodos y variables locales, además de áreas dedicadas para código de bytes y datos estáticos.
  • 📚 Pila vs. Montículo: Los marcos de pila siguen el orden Último en Entrar, Primero en Salir y almacenan tipos primitivos y referencias, mientras que el montón contiene todos los objetos creados con el operador new.
  • ♻️ Recolección de basura: El recolector de basura tracLa accesibilidad de ks se logra mediante cadenas de referencias y se recupera automáticamente la memoria de los objetos a los que ningún hilo puede acceder.
  • Coleccionistas modernos: Las cargas de trabajo de producción dependen cada vez más de G1, ZGC y Shenandoah para la recolección de memoria con pocas pausas en montones de varios gigabytes.
  • 🧪 Ejemplo práctico: Un tutorial paso a paso de la clase Student muestra cuándo las referencias se vuelven aptas para ser recopiladas y cómo la anulación de referencias libera el objeto subyacente.

Gestión de memoria en Java

¿Qué es la memoria de pila en? Java?

Memoria de pila en Java La pila es la región de memoria de la JVM que almacena los marcos de los métodos, las variables locales y las variables de referencia para cada hilo. Siempre se accede a la pila en orden de último en entrar, primero en salir, por lo que el método invocado más recientemente se encuentra en la parte superior, y sus variables locales se insertan y extraen junto con el marco.

Cada hilo en el Java La máquina virtual recibe su propia pila, que por diseño mantiene las invocaciones de métodos aisladas y seguras para subprocesos. Las variables locales primitivas, como int, boolean y double, residen directamente dentro del marco, mientras que las referencias a objetos almacenadas en la pila apuntan a objetos asignados en el montón.

¿Qué es la memoria de montón en Java?

La memoria del montón es la región compartida de la JVM que contiene cada Java Objeto y matriz creados con el operador new, junto con cualquier variable de referencia que pertenezca a esos objetos como campos de instancia. A diferencia de la pila, el montón se comparte entre todos los hilos, por lo que el acceso a los objetos a menudo requiere sincronización.

El montón (Heap) es el área que administra el recolector de basura. Las JVM HotSpot modernas dividen el montón en una generación joven para objetos de corta duración y una generación antigua para objetos de larga duración, con los metadatos de clase almacenados en una región nativa separada llamada Metaspace.

Asignación de memoria en Java

Asignación de memoria en Java Es el proceso mediante el cual la JVM reserva regiones de memoria virtual para variables e instancias de clases y estructuras durante la ejecución del programa. La memoria no se asigna a un objeto en el momento de su declaración; solo se crea una referencia. La asignación real del objeto se realiza mediante el operador `new`, por lo que cada objeto reside en el montón (Heap).

El Java La asignación de memoria se divide en las siguientes secciones:

  1. Montón
  2. Apilar
  3. Code
  4. Estático

Esta división de la memoria es necesaria para una gestión eficaz del tiempo de ejecución.

  • El Code Esta sección contiene su compilado bytecode.
  • El Apilar tiendas por secciones métodos, variables locales y variables de referencia.
  • El Montón la sección contiene objetos y también puede contener variables de referencia almacenadas como campos de instancia.
  • El Estático la sección contiene datos estáticos y métodos estáticos compartido en todas las instancias.

Diferencia entre variable local y de instancia

Comprender dónde reside cada tipo de variable en la JVM ayuda a explicar el comportamiento de la pila y el montón. Instancia variable se declara dentro de una clase pero fuera de cualquier métodoy vive en el Montón como parte de su objeto.

class Student{ 
int num; // num is instance variable 
public void showData{}

A variable local se declara dentro de un método, incluyendo los argumentos del métodoy reside en la pila dentro del marco activo.

public void sum(int a){

int x = a + 3;

// a, x are local variables

}

Diferencia entre pila y montón

La pila y el montón resuelven problemas diferentes dentro de la JVM. La pila proporciona a cada hilo una asignación rápida y determinista para datos de corta duración vinculados al ámbito de los métodos, mientras que el montón ofrece una región compartida para objetos de larga duración a la que cualquier hilo puede acceder. El breve vídeo a continuación resume las diferencias antes del tutorial que sigue.

Haga clic en aquí si el video no es accesible

Para ver cómo cooperan Stack y Heap, consideremos un método principal que llama al método m1.

public void m1{
int x = 20;
}

En la pila de la JVM, se crea un marco para el método m1.

Java Apilar y amontonar

La variable x en m1 también se crea en el marco para m1 en la pila, como se muestra en la imagen a continuación.

Java Apilar y amontonar

El método m1 llama entonces al método m2. En la pila, se crea un nuevo marco para m2 encima del marco de m1.

Java Apilar y amontonar

Java Apilar y amontonar

Las variables locales b y c también se crean dentro del marco para m2 en la pila.

public void m2(int b){
boolean c;
}

A continuación, m2 llama al método m3. Nuevamente, se crea un marco para m3 en la parte superior de la pila, como se muestra a continuación.

Java Apilar y amontonar

Java Apilar y amontonar

Ahora supongamos que el método m3 crea un objeto para la clase Account, que tiene dos variables de instancia int p y int q.

class Account {
    int p;
    int q;
}

Aquí está el código para el método m3.

public void m3(){
    Account ref = new Account();
    // more code
}

La instrucción new Account() crea un objeto de tipo Account en el Heap.

Java Apilar y amontonar

La variable de referencia ref se crea en la pila dentro del marco para m3.

Java Apilar y amontonar

El operador de asignación hace que la variable de referencia apunte al objeto en el Heap.

Java Apilar y amontonar

Una vez que el método finaliza su ejecución, el control regresa al método que lo llamó, que en este caso es el método m2.

Java Apilar y amontonar

El marco para el método m3 se vacía de la pila.

Java Apilar y amontonar

Dado que la variable de referencia ya no apunta al objeto en el Heap, ese objeto pasa a ser apto para la recolección de basura.

Java Apilar y amontonar

Una vez que el método m2 finaliza, se elimina de la pila y todas sus variables dejan de estar disponibles. Lo mismo ocurre con el método m1, y finalmente el control regresa al método principal.

¿Qué ocurre si un objeto contiene otra referencia como variable de instancia?

public static void main(String args[]) {
    A parent = new A();
    // more code
}
class A {
    B child = new B();
    int e;
}
class B {
    int c;
    int d;
}

En este caso, la variable de referencia child reside en el Heap como parte del objeto A, y a su vez apunta a su propio objeto B, como se muestra a continuación.

Java Apilar y amontonar

¿Qué es la recolección de basura en Java?

Recolección de basura en Java Es el proceso mediante el cual la JVM gestiona la memoria automáticamente. El recolector de basura encuentra los objetos que ya no son accesibles desde ninguna referencia activa y recupera su memoria. La asignación dinámica de memoria se realiza mediante el operador `new`, y la memoria permanece asignada hasta que el programa deja de tener referencias al objeto.

Cuando no quedan referencias, el objeto se considera innecesario y la memoria que ocupa puede ser recuperada. No hay necesidad explícita de destruir un objeto porque Java Gestiona la desasignación automáticamente a través del recolector de basura.

La técnica que hay detrás de esto se conoce como Recolección de basuraLos programas que no liberan memoria terminan fallando cuando no queda nada que asignar. Se dice que tales programas tienen pérdidas de memoria. Recolección de basura en Java Se ejecuta automáticamente a lo largo de la vida útil del programa, lo que elimina la carga de la reasignación manual y reduce el riesgo de fugas.

En C, por el contrario, el programador es responsable de liberar la memoria asignada dinámicamente a través de la función free(). Aquí es donde Java La gestión de la memoria ofrece una gran ventaja.

Las JVM HotSpot modernas incluyen varios recolectores de basura optimizados para diferentes cargas de trabajo. El recolector de basura G1 predeterminado busca un rendimiento equilibrado y tiempos de pausa en montones de varios gigabytes. ZGC y Shenandoah buscan pausas de menos de un milisegundo en montones muy grandes, lo que los hace entractivo para servicios sensibles a la latencia. Elegir el recolector adecuado y ajustar los tamaños de Young Generation, Old Generation y Metaspace es una habilidad fundamental para Java trabajo escénico.

Nota: Todos los objetos se crean en la sección de memoria Heap, que es la región que gestiona el recolector de basura.

Ejemplo: aprender el mecanismo del recolector de basura en Java

Este tutorial muestra cuándo las referencias son aptas para ser eliminadas mediante la recolección de basura dentro de un programa sencillo.

Paso 1) Copie el siguiente código en un editor.

class Student{
int a;
int b;
  public void setData(int c,int d){
    a=c;
    b=d;
  }
  public void showData(){
    System.out.println("Value of a = "+a);
    System.out.println("Value of b = "+b);
  }
  public static void main(String args[]){
    Student s1 = new Student();
    Student s2 = new Student();
    s1.setData(1,2);
    s2.setData(3,4);
    s1.showData();
    s2.showData();
    //Student s3;
    //s3=s2;
    //s3.showData();
    //s2=null;
    //s3.showData();
    //s3=null;
    //s3.showData();
  }
}

Paso 2) Guarda, compila y ejecuta el código. Como se muestra en el diagrama, se crean dos objetos y dos variables de referencia.

 Mecanismo recolector de basura

Paso 3) Descomenta las líneas 20, 21 y 22. Guarda, compila y ejecuta el código.

Paso 4) Como se muestra en el diagrama a continuación, dos variables de referencia ahora apuntan al mismo objeto.

Mecanismo recolector de basura

Paso 5) Descomenta las líneas 23 y 24. Guarda, compila y ejecuta el código.

Paso 6) Como se muestra a continuación, s2 se vuelve nulo, pero s3 todavía apunta al objeto, por lo que el objeto aún no es apto para la recolección de basura.

 Mecanismo recolector de basura

Paso 7) Descomenta las líneas 25 y 26. Guarda, compila y ejecuta el código.

Paso 8) En este punto, ninguna referencia apunta al objeto, por lo que este se convierte en objeto de recolección de basura. El recolector de basura lo elimina de la memoria y no hay forma de recuperarlo.

 Aprender recolector de basura

Cómo eliminar un objeto en Java?

Java No proporciona un operador de eliminación manual, por lo que el enfoque estándar es eliminar todas las referencias al objeto para que el recolector de basura pueda recuperarlo.

1) Para que un objeto sea apto para la recolección de basura, asigne el valor nulo a todas las variables de referencia que apunten a él.

2) Los tipos primitivos no son objetos, por lo que no se les puede asignar el valor nulo. Su espacio de almacenamiento se libera automáticamente cuando se elimina el marco de pila que los contiene.

Cómo eliminar un objeto en Java

Preguntas Frecuentes

La pila almacena marcos de métodos, variables locales y referencias en orden Último en Entrar, Primero en Salir por hilo. El montón almacena todos los objetos creados con `new` y se comparte entre hilos, razón por la cual el recolector de basura lo administra.

El recolector de basura recorre las cadenas de referencias desde las raíces del recolector, como los subprocesos activos y los campos estáticos. Cualquier objeto al que no se pueda acceder desde una raíz se considera inaccesible y puede ser recuperado durante el siguiente ciclo de recolección.

La Generación Joven contiene objetos de corta duración en los espacios Eden y Survivor. La Generación Vieja contiene objetos que sobreviven a múltiples colecciones. Metaspace es una región de memoria nativa que almacena metadatos de clase y reemplazó al antiguo PermGen en Java 8.

G1 es el predeterminado y se adapta a la mayoría de las cargas de trabajo en montones de varios gigabytes. ZGC y Shenandoah apuntan a pausas de submilisegundos en montones muy grandes y son buenas opciones para servicios sensibles a la latencia que se ejecutan en Java 17 o posterior.

Establezca en nulo todas las referencias que apunten al objeto, o permita que las referencias queden fuera de ámbito cuando se eliminen sus marcos de pila. Una vez que ninguna referencia activa alcance el objeto, el recolector de basura recupera su memoria del montón.

Sí. Las herramientas de optimización basadas en IA analizan los registros de recolección de basura, las tasas de asignación y los histogramas de pausa para recomendar tamaños de montón, indicadores de recolección y proporciones de generación joven. Acortan el ciclo de retroalimentación en comparación con la optimización manual, especialmente en cargas de trabajo G1, ZGC y Shenandoah.

Las plataformas de observabilidad basadas en IA procesan volcados de memoria y métricas en tiempo real, y luego agrupan los gráficos de objetos retenidos para identificar clases y cadenas de referencia sospechosas. Esto permite detectar posibles fugas de memoria más rápidamente que el análisis manual de la memoria y ayuda a los equipos a determinar la causa raíz en producción.

Resumir este post con: