Running JVM in Kubernetes/OpenShift Pods
About
Running a JVM-based application inside Kubernetes/OpenShift requires proper configuration to optimize memory, CPU, garbage collection, and resource utilization while preventing OOM errors and ensuring stability. Below are best practices categorized for different aspects of JVM tuning in containers.
Memory Management Best Practices
Why? JVM does not automatically respect Kubernetes/OpenShift memory limits unless explicitly configured.
Best Practice
Recommended JVM Options
Explanation
Use MaxRAMPercentageinstead of -Xmx
-XX:MaxRAMPercentage=75.0 (Java 11+)
Sets heap memory dynamically based on container limits instead of a fixed size.
Use InitialRAMPercentagefor predictable startup memory
-XX:InitialRAMPercentage=50.0 (Java 11+)
Ensures a reasonable heap allocation at startup.
Enable container-awareness
-XX:+UseContainerSupport (Java 10+)
Ensures JVM respects cgroups memory limits. (Enabled by default in Java 11+).
Dump heap on OOM errors for debugging
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps/heap.hprof
Allows post-mortem analysis of memory issues.
Exit JVM immediately on OOM to restart the pod
-XX:+ExitOnOutOfMemoryError
Prevents a failing pod from staying alive and consuming resources.
Example for memory-optimized Kubernetes JVM:
java -XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0 -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps/heap.hprof -jar app.jarCPU and Threading Best Practices
Why? By default, JVM detects all available CPU cores, which may lead to excessive thread creation in Kubernetes/OpenShift environments.
Best Practice
Recommended JVM Options
Explanation
Limit CPU usage to avoid pod throttling
-XX:ActiveProcessorCount=<N>
Manually set the number of CPU cores JVM should use.
Set appropriate thread pool sizes
-Djava.util.concurrent.ForkJoinPool.common.parallelism=<N>
Ensures thread pools match available resources.
Enable efficient garbage collection
-XX:+UseG1GC (Default in Java 11+)
Balances performance and latency for most applications.
For latency-sensitive apps, use ZGC
-XX:+UseZGC (Java 17+)
Low-latency GC with minimal pause times, ideal for real-time processing.
Example for CPU-aware Kubernetes JVM:
Garbage Collection (GC) Best Practices
Why? Containers are resource-constrained, and GC tuning prevents excessive memory usage and long pauses.
Best Practice
Recommended JVM Options
Explanation
Use G1GC for balanced performance (Default in Java 11+)
-XX:+UseG1GC
Good for most microservices.
For low-latency workloads, use ZGC (Java 17+)
-XX:+UseZGC
Pause times <1ms, great for real-time processing.
For memory-constrained environments, use Shenandoah GC (Java 17+)
-XX:+UseShenandoahGC
Works well with small heap sizes.
Enable GC logging for monitoring
-Xlog:gc:/var/logs/gc.log
Helps analyze GC behavior inside containers.
Example GC tuning for Kubernetes JVM:
Resource Requests & Limits
Why? Properly defining CPU and memory requests/limits ensures stable performance and prevents resource contention.
Best Practice
Kubernetes YAML Example
Explanation
Set appropriate memory requests & limits
yaml resources: requests: memory: "1Gi" limits: memory: "2Gi"
Ensures JVM doesn’t allocate more memory than the pod allows.
Set CPU limits for predictable performance
yaml resources: requests: cpu: "500m" limits: cpu: "1"
Prevents excessive CPU usage and throttling.
Monitoring & Observability Best Practices
Why? Monitoring JVM inside Kubernetes helps detect performance bottlenecks, memory leaks, and CPU issues.
Best Practice
Tool/Option
Explanation
Enable JMX for real-time monitoring
-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9010
Allows Prometheus/Grafana to collect JVM metrics.
Use Prometheus with Micrometer for JVM metrics
io.micrometer:micrometer-registry-prometheus
Exposes JVM heap, GC, and thread metrics for Kubernetes dashboards.
Enable GC logs for analysis
-Xlog:gc:/var/logs/gc.log
Useful for debugging memory issues.
Example JMX-enabled Kubernetes JVM:
Auto-Restart & Resilience Best Practices
Why? If a JVM crashes or runs out of memory, Kubernetes should automatically restart the pod.
Best Practice
Kubernetes Configuration
Explanation
Set restart policy to Always
yaml restartPolicy: Always
Ensures the pod restarts if JVM crashes.
Use livenessProbe to detect JVM failures
yaml livenessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 10 periodSeconds: 5
Restarts JVM if it becomes unresponsive.
Use readinessProbe to prevent traffic to unhealthy pods
yaml readinessProbe: httpGet: path: /actuator/health port: 8080 initialDelaySeconds: 5 periodSeconds: 5
Ensures pods receive traffic only when fully ready.
Sample Kubernetes Pod Spec
Our pod has:
Memory Request:
1Gi(Minimum guaranteed memory)Memory Limit:
2Gi(Maximum memory allowed)JVM Option:
-XX:MaxRAMPercentage=75.0(JVM will use 75% of available memory)
Stage
Kubernetes Perspective
JVM Perspective
Pod Scheduling
Kubernetes ensures the node has at least 1Gi RAM available.
JVM is not aware of requests, only limits.
Memory Allocation
Pod starts with memory between 1Gi and 2Gi, based on availability.
JVM calculates max heap as 75% of available RAM.
Heap Calculation
Pod cannot exceed 2Gi. If exceeded, pod is killed (OOMKilled).
JVM takes 75% of 2Gi, so JVM heap max = 1.5Gi. Remaining 0.5GiB is used by JVM metaspace, thread stacks, GC, etc.
Last updated