For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.jar

CPU 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.

Requests

  • The minimum amount of CPU/memory allocated to a container.

  • Kubernetes ensures that the container always gets at least this amount.

  • If a node does not have enough requested resources, Kubernetes won't schedule the pod.

Limits

  • The maximum amount of CPU/memory a container can use.

  • If a container exceeds the memory limit, it gets killed (OOMKilled).

  • If it exceeds CPU limit, it gets throttled (not killed).

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