Gestión de memoria

Heap, referencias y el recolector de basura

Stack y heap

La JVM gestiona dos áreas de memoria principales. El stack almacena variables locales y marcos de llamada — se gestiona automáticamente y es muy rápido. El heap almacena todos los objetos — es gestionado por el recolector de basura (GC) y compartido entre todos los hilos.

// Memoria en Java: pila (stack) y montón (heap)

// Stack: variables locales y marcos de llamada — automático, rápido
void metodo() {
    int x = 5;          // primitivo: valor directo en el stack
    String s = "hola";  // referencia en el stack, objeto String en el heap
}
// Al salir de metodo(), x y s se eliminan del stack automáticamente

// Heap: objetos — gestionado por el GC
Persona p = new Persona("Ana"); // objeto en el heap
// p es una referencia (en stack o en otro objeto) que apunta al heap

Tipos de referencias

Java tiene cuatro tipos de referencias que difieren en cómo interactúan con el GC. En el 99% del código se usan referencias fuertes. Las débiles y blandas son útiles para implementar caches que no provoquen OutOfMemoryError.

// Tipos de referencias y ciclo de vida

// Referencia fuerte (strong): la más común — el GC NO recolecta el objeto
Persona p = new Persona("Ana"); // referencia fuerte
p = null; // elimina la referencia; si no hay más referencias al objeto, es elegible para GC

// Referencia débil (WeakReference): el GC puede recolectar el objeto en cualquier ciclo
import java.lang.ref.WeakReference;
WeakReference<Persona> weak = new WeakReference<>(new Persona("Luis"));
Persona persona = weak.get(); // null si ya fue recolectado

// SoftReference: como WeakReference pero el GC la respeta mientras haya memoria suficiente
// Útil para caches

// PhantomReference: para limpieza post-finalización (uso muy avanzado)

El recolector de basura

El GC identifica objetos que no son alcanzables desde ninguna referencia viva y libera su memoria. No se puede forzar ni predecir cuándo ejecuta — System.gc() es una sugerencia, no una garantía. El GC no elimina memory leaks: solo los objetos sin referencias son elegibles.

// El Garbage Collector (GC): recolecta objetos sin referencias accesibles

// Un objeto es elegible para GC cuando no hay ninguna referencia fuerte hacia él:
void crearObjeto() {
    Persona p = new Persona("temporal");
    // p es local — al salir de este método, el objeto puede ser recolectado
}

// Una referencia en un campo de instancia mantiene vivo al objeto mientras viva el contenedor
public class Servidor {
    private List<Conexion> conexiones = new ArrayList<>();

    public void agregarConexion(Conexion c) {
        conexiones.add(c); // c no puede ser recolectado mientras Servidor esté vivo
    }

    public void cerrarConexion(Conexion c) {
        conexiones.remove(c); // ahora c puede ser recolectado
    }
}

Memory leaks en Java

// Memory leaks en Java: el GC no puede ayudar si hay referencias activas innecesarias

// Anti-patrón: lista estática acumulando objetos sin limpiar
public class Cache {
    private static final List<byte[]> cache = new ArrayList<>();

    public void guardar(byte[] datos) {
        cache.add(datos); // se acumula indefinidamente
    }
    // Solución: usar WeakHashMap, SoftReference, o limpiar explícitamente
}

// Anti-patrón: listeners no desregistrados
button.addActionListener(miListener);
// Si el listener tiene referencia a un objeto grande y nunca se elimina:
button.removeActionListener(miListener); // necesario para liberar la referencia

// Herramienta de diagnóstico: jmap, jvisualvm, Eclipse Memory Analyzer (MAT)

OutOfMemoryError y configuración del heap

// OutOfMemoryError: cuando el heap se agota
// Síntomas: la app va lenta, luego muere con java.lang.OutOfMemoryError: Java heap space

// Configurar el tamaño del heap:
// java -Xms256m -Xmx1g MiAplicacion
// -Xms: tamaño inicial del heap (256 MB)
// -Xmx: tamaño máximo del heap (1 GB)

// GC moderno de Java 8: Parallel GC por defecto
// Alternativas: -XX:+UseG1GC (G1, mejor para heaps grandes)
//               -XX:+UseConcMarkSweepGC (CMS, latencia baja — deprecated en Java 9+)
// Java 11+: G1GC es el predeterminado
En la práctica: no gestiones la memoria manualmente. El GC lo hace por ti. Céntrate en no retener referencias innecesarias: desregistra listeners, limpia colecciones estáticas y usa try-with-resources para recursos externos. Si hay problemas de memoria, analiza con herramientas (jmap, VisualVM, MAT) antes de ajustar el heap.

Siguiente apartado → Generics: parámetros de tipo, bounds y wildcards

Índice de la sección

Índice del curso