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.
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.jarCuándo usar: Aplicaciones de un solo hilo, heap < 100 MB
2. Parallel GC (predeterminado en Java 8)
java -XX:+UseParallelGC -jar myapp.jarCuá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.jarCuándo usar: Heap > 4 GB, se necesitan pausas predecibles
Configuración del tiempo de pausa objetivo:
java -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar myapp.jar4. 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.jarCuá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.jarPerfilado: 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.jarLuego 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.jarProblema 2: OutOfMemoryError: Java heap space
Solución:
Aumentar
-XmxEncuentra fugas de memoria a través de heap dump
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.jarProblema 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.jar2. Registro de GC
-Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=100m3. 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.password4. 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: 2G5. 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.jarHerramientas 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!
