💥 ¡Socorro! Mi app Java se ha tragado toda la memoria… ¿Y ahora qué?


¿Tu aplicación Java se ha vuelto más lenta que un caracol con resaca? ¿Te ha saludado el temido OutOfMemoryError
como quien no quiere la cosa? 😱
¡Tranquilidad, joven padawan del código! En este artículo vamos a entender cómo funciona la memoria en la JVM, qué puede salir mal (spoiler: muchas cosas), y cómo puedes arreglarlo sin tirarte de los pelos.
🧠 ¿Cómo piensa la JVM? Entendiendo su memoria
Imagina que la JVM es como una casa de estudiantes:
- Heap 🛋️: El salón común donde todos los objetos viven y conviven. Aquí se almacenan cosas como
new Gato()
onew Pizza()
. - Metaspace 📚: La biblioteca de la casa. Guarda información sobre las clases, métodos, y demás sabiduría Java.
- Code Cache 💾: Una caja secreta donde la JVM guarda código nativo compilado para que tu app corra como Sonic.
- Stack del hilo 🎒: Cada hilo tiene su mochila donde guarda sus variables y los pasos que va siguiendo.
- PC Register 🧭: Es la brújula de cada hilo, que indica qué línea de código toca ejecutar ahora.
- Memoria nativa 🍳: Cocina compartida. Se usa para JNI y otras cosas “de fuera” de Java.
Y ahora entiendes por qué Java usa más memoria de la que tú pensabas. ¡El heap es solo una habitación!
♻️ ¿Qué pasa con la basura? (¡del heap, no del piso!)
Java es como ese compañero de piso ordenado que siempre está recogiendo todo lo que ya no se usa. Esto lo hace gracias al famoso Garbage Collector (GC).
El Heap está dividido en:
Generación Joven (Young) 👶:
- Eden: Donde nacen los objetos.
- Survivor S0/S1: Como una prueba de resistencia. Si sobreviven varios GC, van al siguiente nivel.
Generación Vieja (Old) 👵: Donde van los objetos longevos, como
List<String> usuariosPremium
.
Cómo funciona esto:
- Nace un objeto:
Pizza margarita 🍕
vive en Eden. - ¡Boom! Llega un GC menor y borra los objetos que nadie usa (bye
Pizza hawaiana 🍍
). - Si
Pizza margarita
sigue viva, se muda a un Survivor. - Tras varias mudanzas, se jubila en la Generación Vieja.
Así Java optimiza el rendimiento. ¡La mayoría de objetos en Java duran menos que una historia de Instagram!
🔗 ¿Y el stack? ¿Y los hilos?
Cada hilo tiene su propia mochila donde guarda:
- Variables locales
- Llamadas a métodos
- Referencias a objetos
Pero los objetos viven en el heap compartido. Así todos los hilos pueden compartir una misma TartaDeChocolate
🍫, si tienen la referencia.
Cuando un método termina, la variable desaparece del stack… ¡pero el objeto sigue ahí hasta que el GC lo recoja!
🚨 Problemas de memoria típicos (y cómo evitarlos sin llorar)
1. Fugas de memoria 😬
Tienes objetos que ya no necesitas, pero aún están referenciados. Como guardar Gato muerto
🐱 en una lista y olvidarte de borrarlo. El GC no lo puede eliminar.
2. Creación de objetos descontrolada 🎉
Crear objetos sin parar, como un new Fiesta()
cada segundo, puede saturar el heap. ¡Y boom! OutOfMemoryError
.
3. Tamaños mal configurados
- Heap pequeño = GCs constantes = tu app suena como una impresora de los 90.
- Heap enorme = GC lentísimo = pausas de café... muy largas.
4. Fugas nativas
Cuidado con lo que haces con JNI o buffers nativos. El GC no puede ayudarte ahí.
5. Demasiados hilos 🍝
Cada hilo necesita su stack. Si tienes 3000 hilos, la memoria va a petar más rápido que un globo bajo el sol.
🧪 Herramientas mágicas para detectar problemas
🧭 JVisualVM
Herramienta con interfaz gráfica que te muestra:
- Uso de heap
- Threads
- CPU
- Y hasta puedes hacer dumps de memoria (como CSI: JVM Edition)
👉 https://visualvm.github.io/download.html
🔬 Java Mission Control + JFR
Recoge datos al vuelo con bajo impacto en el rendimiento. Sirve para analizar:
- Cuellos de botella
- Fugas
- Consumo de threads
🧰 Eclipse MAT
Ideal para abrir heap dumps
y ver qué objeto está reteniendo memoria como si fuera Gollum con su anillo.
👉 https://www.eclipse.org/mat/
📈 Analiza tus logs de GC como un pro
Activa los logs con:
java -Xlog:gc* -jar tu-app.jar
Podrás ver:
- Cuándo ocurren los GC
- Cuánta memoria limpian
- Qué tanto pausa tu aplicación
Así podrás saber si tu GC
es un héroe eficiente... o un desastre ambulante.
💣 ¿Tienes un OutOfMemoryError? ¡Haz un dump!
Activa esto en producción (de verdad, hazlo):
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/ruta/del/volcán.hprof
Luego abre el .hprof
con MAT o VisualVM y encuentra al culpable. ¡A veces es un Map
que crece más que un Tamagotchi sin límites!
🔍 ¿Y la memoria nativa?
Activa el tracking:
-XX:NativeMemoryTracking=summary
Luego usa jcmd
para sacar un reporte:
jcmd <pid> VM.native_memory summary
Así verás si algún buffer o librería nativa está bebiendo más memoria de la cuenta.
🎯 Conclusión
Java es como una casa muy organizada, pero si no entiendes cómo distribuye las habitaciones y quién recoge la basura… vas a tener problemas. Aprender cómo funciona la memoria y usar las herramientas adecuadas te puede ahorrar horas de frustración (y muchos cafés innecesarios ☕).
¿Tienes dudas o quieres que explique alguna herramienta con dibujitos? ¡Déjamelo en los comentarios o suscríbete a la newsletter para más pildoritas Java! 😄
Happy coding y recuerda: si algo se mueve y no debería, ¡es un bug!
Subscribe to my newsletter
Read articles from Carlos Exposito directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
