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