All Products
Search
Document Center

Container Service for Kubernetes:Collect JVM heap dumps on abnormal exits with CNFS

Last Updated:Jun 18, 2026

Capture .hprof heap dumps that persist across OOM pod restarts by mounting a CNFS-backed NAS volume.

Prerequisites

Ensure the following:

Usage notes

Set the JVM max heap below the pod memory limit. Keep -Xmx below the pod memory limit. If the JVM heap hits the pod limit first, the Linux OOM killer terminates the pod before the JVM can write the dump.

Use a dedicated CNFS for heap dumps. A single OOM event can produce a multi-gigabyte .hprof file that may exhaust shared quota and disrupt operations.

Sync the File Browser image before you start. docker.io/filebrowser/filebrowser:v2.18.0 may fail to pull due to network restrictions. Sync it to your ACR Enterprise Edition instance by subscribing to images from outside China:

Field Value
Artifact source Docker Hub
Source repository coordinates filebrowser/filebrowser
Subscription policy v2.18.0
After syncing, configure password-free image pulling between the ACR Enterprise Edition instance and your ACK cluster.

Deploy the Java application

Deploy a Java Deployment with CNFS-backed storage as the heap dump destination. The sample image registry.cn-hangzhou.aliyuncs.com/acs1/java-oom-test:v1.0 runs Mycode with an 80 MiB heap limit.

The Deployment uses subPathExpr: $(POD_NAMESPACE).$(POD_NAME) to create per-pod subdirectories inside the shared NAS volume, preventing pod restarts from overwriting each other's dumps.

subPathExpr uses round brackets — $(POD_NAME) — not curly brackets. Values come from the Downward API environment variables POD_NAME and POD_NAMESPACE.
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: java-application
spec:
  selector:
    matchLabels:
      app: java-application
  template:
    metadata:
      labels:
        app: java-application
    spec:
      containers:
      - name: java-application
        image: registry.cn-hangzhou.aliyuncs.com/acs1/java-oom-test:v1.0
        imagePullPolicy: Always
        env:
        - name: POD_NAME          # Inject pod name via Downward API
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: POD_NAMESPACE     # Inject pod namespace via Downward API
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        args:
        - java
        - -Xms80m                         # Minimum heap size
        - -Xmx80m                         # Maximum heap size (keep below pod memory limit)
        - -XX:HeapDumpPath=/mnt/oom/logs  # Write heap dumps to the CNFS-backed mount
        - -XX:+HeapDumpOnOutOfMemoryError # Trigger heap dump on OOM
        - Mycode
        volumeMounts:
        - name: java-oom-pv
          mountPath: "/mnt/oom/logs"
          subPathExpr: $(POD_NAMESPACE).$(POD_NAME)  # Round brackets, not curly brackets
      volumes:
      - name: java-oom-pv
        persistentVolumeClaim:
          claimName: cnfs-nas-pvc
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cnfs-nas-pvc
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: alibabacloud-cnfs-nas
  resources:
    requests:
      storage: 70Gi  # If directory quota is enabled, limits the subdirectory to 70 GiB
---
EOF

Verify the OOM event

Once the Deployment starts, Mycode allocates memory until the JVM exhausts the 80 MiB heap and triggers OOM. The pod restarts, and ACK records a back-off restarting alert in Event Center.

To confirm the OOM occurred:

  1. Log on to the ACK console. In the left navigation pane, click Clusters.

  2. Find your cluster and click its name. In the left navigation pane, choose Operations > Event Center.

  3. Check the events list for a back-off restarting alert on the java-application pod.

    3e0492283c067026c9cfd348a898ecb1

Browse the heap dump files

Deploy File Browser with the same CNFS PVC mounted at rootDir to browse and download heap dumps.

Deploy File Browser

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: filebrowser
  namespace: default
  labels:
    app.kubernetes.io/instance: filebrowser
    app.kubernetes.io/name: filebrowser
data:
  .filebrowser.json: |
    {
      "port": 80,
      "address": "0.0.0.0"
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: filebrowser
  namespace: default
  labels:
    app.kubernetes.io/instance: filebrowser
    app.kubernetes.io/name: filebrowser
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/instance: filebrowser
      app.kubernetes.io/name: filebrowser
  template:
    metadata:
      labels:
        app.kubernetes.io/instance: filebrowser
        app.kubernetes.io/name: filebrowser
    spec:
      containers:
      - name: filebrowser
        # Replace with your ACR image address after syncing filebrowser/filebrowser:v2.18.0
        image: XXXX-registry-vpc.cn-hangzhou.cr.aliyuncs.com/test/test:v2.18.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        volumeMounts:
        - mountPath: /.filebrowser.json
          name: config
          subPath: .filebrowser.json
        - mountPath: /db
          name: rootdir
        - mountPath: /rootdir
          name: rootdir
      volumes:
      - name: config
        configMap:
          name: filebrowser
          defaultMode: 420
      - name: rootdir
        persistentVolumeClaim:
          claimName: cnfs-nas-pvc  # Same PVC as the java-application Deployment
EOF

Expected output:

configmap/filebrowser unchanged
deployment.apps/filebrowser configured

Create a Service

  1. Log on to the ACK console. In the left navigation pane, click Clusters.

  2. Find your cluster and click its name. In the left navigation pane, choose Network > Services.

  3. On the Services page, select the default namespace and click Create. Configure the following parameters:

    See NLB billing overview.
    Parameter Value
    Name filebrowser
    Service type SLB > SLB type: CLB. Select Create Resource, then set Access method to Public Access.
    Backend Click +Reference Workload Label. Set Resource type to Deployments and Resources to filebrowser.
    Port mapping Service port: 8080, Container port: 80, Protocol: TCP
  4. Submit the configuration.

Access File Browser

  1. Copy the endpoint address from the Services page.

  2. Open <endpoint-address>:8080 in a browser. The File Browser login page appears.

  3. Log in with the default credentials: username admin, password admin.

    20fe4dcde1759ebc64cbe0b1bb3168da

  4. Double-click rootdir to enter the NAS mount point.

    image

Result

Inside rootdir, each pod's dump directory is named by the subPathExpr: $(POD_NAMESPACE).$(POD_NAME) rule — for example, default.java-application-76d8cd95b7-prrl2.

image

Open the directory to find java_pid1.hprof. Download and analyze it with Eclipse Memory Analyzer (MAT) to identify the code that caused the OOM.

lQLPJxMFSGyoLcnNAqTNB2awoAbbe3-kh8AIV2X4pMttAA_1894_676

How it works

Three mechanisms cooperate to persist heap dumps across pod restarts:

  • CNFS PVC with `ReadWriteMany`: The NAS-backed PVC mounts into both the java-application and File Browser pods. NAS persists files independently of pod lifecycle, so dump files survive pod crashes.

  • `subPathExpr` with Downward API: Each pod mounts into its own subdirectory (<namespace>.<pod-name>) rather than the NAS root, preventing restarts from overwriting dumps and making it easy to trace dumps to pods.

  • `-XX:+HeapDumpOnOutOfMemoryError`: The JVM writes the heap state to -XX:HeapDumpPath before exiting on OOM. Because that path is NAS-backed, the file survives the pod exit.