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

Carlos ExpositoCarlos Exposito
5 min read

¿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() o new 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:

  1. Nace un objeto: Pizza margarita 🍕 vive en Eden.
  2. ¡Boom! Llega un GC menor y borra los objetos que nadie usa (bye Pizza hawaiana 🍍).
  3. Si Pizza margarita sigue viva, se muda a un Survivor.
  4. 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!

0
Subscribe to my newsletter

Read articles from Carlos Exposito directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Carlos Exposito
Carlos Exposito