{}const=>[]async()letfn</>var
DesarrolloPráctica

Optimización de JVM: de una aplicación lenta a una producción de combate en 30 minutos

En esta guía detallada, analizaremos cómo configurar correctamente la JVM, elegir un recolector de basura, perfilar una aplicación y prepararla para cargas reales. Ejemplos prácticos, configuraciones listas para usar y una lista de verificación para la producción: todo lo que necesita un desarrollador novato.

К

Kodik

Autor

5 min de lectura

Anatomía de la memoria JVM: entendiendo Heap.

Heap (pila) es un área de memoria donde se almacenan todos los objetos de tu aplicación. Se divide en varias áreas:

Estructura de Heap:

Young Generation (generación joven):

  • Eden Space — aquí se crean nuevos objetos

  • Survivor Space (S0 y S1) — objetos que sobrevivieron a la primera recolección de basura

Old Generation (generación antigua):

  • Objetos de larga duración que han sobrevivido a varios ciclos de recolección de basura

Metaspace (reemplazó a PermGen en Java 8+):

  • Metadatos de clases, constantes

¿Cómo configurar el tamaño de Heap?

# Establecer el tamaño mínimo y máximo de la pila
java -Xms2g -Xmx4g -jar myapp.jar

# -Xms — tamaño inicial del heap (2 GB)
# -Xmx — tamaño máximo de la pila (4 GB)

Regla importante: Xms y Xmx es mejor hacerlos iguales en producción para evitar cambiar el tamaño del heap durante la ejecución.

¿Cómo calcular el tamaño correcto?

Para empezar, puedes usar la fórmula:

Heap Size = (Peak Live Data Size) × (2-4)

Donde Peak Live Data Size es la cantidad máxima de objetos "vivos" después de Full GC.

🔥 100.000+ estudiantes ya están con nosotros

¿Cansado de leer teoría?
¡Hora de programar!

Kodik — una app donde aprendes a programar con práctica. Mentor IA, lecciones interactivas, proyectos reales.

🤖 IA 24/7
🎓 Certificados
💰 Gratis
🚀 Empezar
Se unieron hoy

Garbage Collection: elegir y configurar

GC (Garbage Collector) elimina automáticamente los objetos no utilizados de la memoria. Hay varios tipos de recolectores de basura, cada uno con sus propias características.

Principales tipos de GC:

1. Serial GC (para aplicaciones pequeñas)

java -XX:+UseSerialGC -jar myapp.jar

Cuándo usar: Aplicaciones de un solo hilo, heap < 100 MB

2. Parallel GC (predeterminado en Java 8)

java -XX:+UseParallelGC -jar myapp.jar

Cuándo usar: Aplicaciones donde el rendimiento es importante (throughput), se pueden tolerar pausas

3. G1GC (recomendado para la mayoría de los casos)

java -XX:+UseG1GC -jar myapp.jar

Cuándo usar: Heap > 4 GB, se necesitan pausas predecibles

Configuración del tiempo de pausa objetivo:

java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar

4. ZGC y Shenandoah (para aplicaciones de baja latencia)

# ZGC (Java 11+, listo para producción con Java 15)
java -XX:+UseZGC -jar myapp.jar

# Shenandoah
java -XX:+UseShenandoahGC -jar myapp.jar

Cuándo usar: Pausas mínimas críticas (< 10 ms), gran heap

Monitoreo de GC en registros

Habilitar registros detallados de GC:

java -Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=100m \
     -XX:+UseG1GC \
     -jar myapp.jar

Perfilado: encontrar cuellos de botella

El análisis de rendimiento ayuda a comprender dónde gasta tiempo y memoria tu aplicación.

1. Usamos JVisualVM (gratis)

JVisualVM está incluido en JDK y permite:

  • Supervisar el uso de la CPU y la memoria en tiempo real

  • Tomar volcados de memoria

  • Analizar los flujos

# Ejecutar la aplicación con JMX
java -Dcom.sun.management.jmxremote \
     -Dcom.sun.management.jmxremote.port=9010 \
     -Dcom.sun.management.jmxremote.authenticate=false \
     -Dcom.sun.management.jmxremote.ssl=false \
     -jar myapp.jar

Luego conéctate a través de VisualVM a localhost:9010.

2. Análisis de Heap Dump

Cuando se produce un OutOfMemoryError, es útil obtener una instantánea de la memoria:

# Volcado automático en OOM
java -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/tmp/heapdump.hprof \
     -jar myapp.jar

# O crea manualmente
jmap -dump:live,format=b,file=heap.bin <PID>

Analiza el volcado en Eclipse MAT o VisualVM, busca:

  • Objetos que ocupan más memoria

  • Fugas de memoria (memory leaks)

  • Colecciones inesperadamente grandes

3. Async Profiler (production-ready)

Para la producción, async-profiler es adecuado, no ralentiza la aplicación:

# Descargar
wget https://github.com/jvm-profiling-tools/async-profiler/releases/latest/download/async-profiler-2.9-linux-x64.tar.gz

# Ejecutar el perfilado durante 60 segundos
./profiler.sh -d 60 -f flamegraph.html <PID>

El resultado es un flamegraph, donde puedes ver los métodos «calientes».

4. Seguimiento de métricas

Utilice Micrometer + Prometheus + Grafana:

// Spring Boot exporta automáticamente las métricas de JVM
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Añadir a application.properties:

management.endpoints.web.exposure.include=prometheus,health,metrics
management.metrics.export.prometheus.enabled=true

Problemas típicos y sus soluciones

Problema 1: Full GC frecuentes

Síntomas: La aplicación se «congela» durante unos segundos

Solución:

# Aumenta el heap o configura la generación joven
java -Xms4g -Xmx4g \
     -XX:NewRatio=2 \
     -XX:+UseG1GC \
     -jar myapp.jar

Problema 2: OutOfMemoryError: Java heap space

Solución:

  1. Aumentar -Xmx

  2. Encuentra fugas de memoria a través de heap dump

  3. Optimiza el código (deshazte de las colecciones innecesarias)

Problema 3: OutOfMemoryError: Metaspace

Solución:

# Aumenta el metaspace
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -jar myapp.jar

Problema 4: Alta carga de la CPU debido a GC

Solución:

  • Cambie a un GC más eficiente (G1, ZGC)

  • Aumenta el heap

  • Optimiza la creación de objetos en el código

Lista de verificación de preparación para la producción

1. Ajustes de memoria

java -Xms4g -Xmx4g \                    # Heap fijo
     -XX:MetaspaceSize=256m \           # Metaspace
     -XX:MaxMetaspaceSize=512m \
     -XX:+UseG1GC \                     # G1 GC
     -XX:MaxGCPauseMillis=200 \         # Tiempo de pausa objetivo
     -XX:+HeapDumpOnOutOfMemoryError \  # Volcado en caso de OOM
     -XX:HeapDumpPath=/var/log/app \
     -jar myapp.jar

2. Registro de GC

-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=100m

3. JMX para el seguimiento

-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.authenticate=true \
-Dcom.sun.management.jmxremote.ssl=true \
-Dcom.sun.management.jmxremote.password.file=/etc/jmx.password

4. Contenedorización (Docker)

JVM determina automáticamente los límites del contenedor con Java 10+:

FROM openjdk:17-slim

# JVM verá automáticamente los límites
ENV JAVA_OPTS="-XX:+UseContainerSupport \
               -XX:MaxRAMPercentage=75.0 \
               -XX:+UseG1GC"

CMD java $JAVA_OPTS -jar app.jar
# docker-compose.yml
services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          memory: 2G

5. Supervisión en producción

Asegúrate de hacer un seguimiento de:

  • Heap usage (memoria utilizada)

  • GC frequency (frecuencia de montaje)

  • GC pause time (duración de las pausas)

  • Thread count (número de flujos)

  • CPU usage

Ejemplo de configuración terminada

Para una aplicación típica de Spring Boot:

#!/bin/bash
java -Xms2g -Xmx2g \
     -XX:MetaspaceSize=256m \
     -XX:MaxMetaspaceSize=256m \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/myapp/heapdump.hprof \
     -Xlog:gc*:file=/var/log/myapp/gc.log:time,uptime:filecount=10,filesize=100m \
     -Dcom.sun.management.jmxremote \
     -Dcom.sun.management.jmxremote.port=9010 \
     -Dcom.sun.management.jmxremote.authenticate=false \
     -Dcom.sun.management.jmxremote.ssl=false \
     -jar myapp.jar

Herramientas adicionales

  • GCEasy (gceasy.io) — análisis de registros de GC en línea

  • FastThread (fastthread.io) — análisis de volcados de hilos

  • JProfiler — perfilador comercial

  • YourKit — otro gran perfilador comercial

  • Arthas — herramienta de Alibaba para el diagnóstico en tiempo de ejecución

Conclusiones

La optimización de JVM es un proceso iterativo. Comienza por comprender los conceptos básicos (heap, GC, profiling), usa las herramientas de diagnóstico adecuadas y optimiza gradualmente para tu carga de trabajo específica.

Recuerda: la optimización prematura es la raíz de todos los males. ¡Primero mida, luego optimice!

¿Te ha sido útil?

Únete a Kodiku — ¡la plataforma educativa para desarrolladores!

Aquí encontrarás cursos estructurados, tareas prácticas y materiales actualizados sobre Java, optimización del rendimiento y preparación para la producción.

Y también tenemos genial canal de Telegram con una comunidad amistosa, donde desarrolladores experimentados comparten conocimientos, ayudan a resolver problemas y discuten las mejores prácticas. ¡Únete!

🎯Deja de postergar

¿Te gustó el artículo?
¡Hora de practicar!

En Kodik no solo lees — escribes código de inmediato. Teoría + práctica = habilidades reales.

Práctica instantánea
🧠IA explica código
🏆Certificado

Sin registro • Sin tarjeta