Skip to content

AlliotTech/oom-java-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java OOM Simulation for cgroup v1 vs. cgroup v2

This project demonstrates the difference in behavior for a Java application experiencing an Out-of-Memory (OOM) condition in a container environment, specifically simulating the issues seen when migrating from cgroup v1 to cgroup v2 on platforms like EKS.

  • The "Bad" Scenario (cgroup v1 simulation): An older or misconfigured JVM is not aware of the container's memory limits. It attempts to use more memory than allocated, causing the container runtime to abruptly kill the process (OOMKilled). The application has no chance to handle the error gracefully.
  • The "Good" Scenario (cgroup v2): A modern, container-aware JVM correctly detects the memory limits. It respects these limits and throws a catchable java.lang.OutOfMemoryError when the heap is exhausted, allowing the application to shut down gracefully.

How This Project Simulates the Issue

This project is pre-configured to demonstrate the "bad" scenario.

  1. src/main/java/com/example/oom/OomApp.java: A simple Java app that continuously allocates 1MB chunks of memory to the heap.
    • It prints the JVM's max heap size on startup.
    • It has a try-catch block to gracefully handle OutOfMemoryError.
  2. Dockerfile: Builds the Java app and configures the runtime.
    • It uses ENV JAVA_OPTS="-XX:-UseContainerSupport ..." to explicitly disable the JVM's container awareness. This is the key to simulating the cgroup v1 problem. The JVM will now base its heap size on the node's memory, not the container's limit.
  3. k8s/deployment.yaml: Deploys the application to Kubernetes with a 256Mi memory limit.

How to Reproduce the Problem (OOMKilled)

  1. Build and Push the Docker Image

    # Navigate to the project root directory
    cd oom-java-app
    
    # Build the image (replace with your registry if needed)
    docker build -t alliot/oom-jdk11:latest .
    
    # Push the image
    docker push alliot/oom-jdk11:latest
  2. Deploy to Kubernetes

    kubectl apply -f k8s/deployment.yaml
  3. Observe the Failure

    # Find your pod name
    POD_NAME=$(kubectl get pods -l app=oom-app -o jsonpath='{.items[0].metadata.name}')
    
    # Watch the logs
    kubectl logs -f $POD_NAME

    You will see:

    • The application starts.
    • The JVM Max Heap Size (Xmx) will be a large value (e.g., >1000 MB), ignoring the 256Mi limit from the deployment YAML.
    • The application will log memory allocations.
    • The logs will suddenly stop after allocating around 256 MB. The graceful OutOfMemoryError message is never printed.
  4. Confirm the OOMKilled Status

    kubectl describe pod $POD_NAME

    In the output, you will see that the pod's State is Terminated with Reason: OOMKilled.

How to See the Correct Behavior (Graceful OOM)

Now, let's reconfigure the JVM to be container-aware, as it should be in a cgroup v2 environment.

  1. Modify the Dockerfile

    • Open the Dockerfile.
    • Comment out or change the ENV JAVA_OPTS line to enable container support:
      # ENV JAVA_OPTS="-XX:-UseContainerSupport -XX:+UnlockDiagnosticVMOptions -Xlog:os+container=debug"
      ENV JAVA_OPTS="-XX:+UseContainerSupport"
      (Note: For modern JVMs, +UseContainerSupport is the default, so you could also just remove the ENV line entirely).
  2. Rebuild, Push, and Redeploy

    # Rebuild and push the image with the same tag
    docker build -t alliot/oom-jdk11:latest .
    docker push alliot/oom-jdk11:latest
    
    # Delete the old deployment to ensure the new image is pulled
    kubectl delete deployment oom-app-deployment
    
    # Re-apply the deployment
    kubectl apply -f k8s/deployment.yaml
  3. Observe the Success

    # Find the new pod name
    POD_NAME=$(kubectl get pods -l app=oom-app -o jsonpath='{.items[0].metadata.name}')
    
    # Watch the logs
    kubectl logs -f $POD_NAME

    You will now see:

    • The JVM Max Heap Size (Xmx) is a much smaller, more reasonable number (e.g., ~170 MB), as it respects the 256Mi container limit.
    • The application allocates memory until it hits the JVM's heap limit.
    • The SUCCESS: JVM gracefully caught OutOfMemoryError! message is printed to the console.
    • The pod exits with a Completed status, not OOMKilled.

About

Java OOM Simulation for cgroup v1 vs. cgroup v2

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published