The heap had room, but the service stopped
Find the culprit in a heap dump
Goal
Create an OutOfMemoryError yourself, find the culprit with a heap dump and a class histogram, and leave the startup options that prevent recurrence in a script.
Why it matters
A dump is created only at the moment of the incident. If you do not turn the option on, there is nothing after the incident. And an OutOfMemoryError does not kill the process — ExitOnOutOfMemoryError ends the state of being half dead and passing the health check. The material Leak.java keeps putting arrays of size -Dleak.kb=256 into a static map (it does not put them in with -Dleak.retain=false), sets the speed with -Dleak.sleep.ms, and the number of iterations with -Dleak.iterations. It prints max_heap_mb= at start.
Steps
-
Compile Leak.java in
/root/jvm/heapand run it withjava -Xmx64m -cp /root/jvm/heap Leak > /root/jvm/heap/oom.log 2>&1. The log must containjava.lang.OutOfMemoryError: Java heap space. -
Run it again with
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/jvm/heap/oom.hprofadded, and save the output to/root/jvm/heap/oom-dump.log.oom.hprofmust be created (1MB or more, starting withJAVA PROFILE 1.0.2) and the log must containHeap dump file created. -
Start
java -Xmx64m -Dleak.sleep.ms=50 -cp /root/jvm/heap Leakin the background and after 2 seconds savejcmd <pid> GC.class_histogram > /root/jvm/heap/histo.txtandjcmd <pid> GC.heap_dump /root/jvm/heap/live.hprof(you may kill the process afterward). -
From the first-ranked line in histo.txt, read the class name and instance count and write them to
/root/jvm/heap/top.txtas two lines,top_class=<이름>(the class name) andinstances=<n>. -
Run the same allocation without references with
java -Xmx64m -Dleak.retain=false -Dleak.iterations=3000 -cp /root/jvm/heap Leak > /root/jvm/heap/noleak.log 2>&1. The log must containdone iterations=3000and no OutOfMemoryError. -
Run it with
-XX:+ExitOnOutOfMemoryErroradded, save the output to/root/jvm/heap/exit-on-oom.logand the exit code (echo $?) to/root/jvm/heap/exit.txt. The log must containTerminating due to java.lang.OutOfMemoryErrorand exit.txt must be 3. -
From the output of
java -XX:+PrintFlagsFinal -version, read theMaxHeapSizevalue and the value with-XX:MaxRAMPercentage=50added, and write them to/root/jvm/heap/ram.txtas two lines,default_max_heap=<바이트>andhalf_max_heap=<바이트>(the placeholder being a number of bytes). -
Create
/root/jvm/heap/start.sh: a script that runs Leak with-Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/jvm/heap/dumps -XX:+ExitOnOutOfMemoryError -Xlog:gc:file=/root/jvm/heap/gc.logand writes the exit code to/root/jvm/heap/runbook-exit.txt. When you run it, a .hprof must be created in/root/jvm/heap/dumps/, gc.log must be created, and runbook-exit.txt must be 3.
Notes
- Checking the HPROF header:
dd if=/root/jvm/heap/oom.hprof bs=1 count=18 2>/dev/null→JAVA PROFILE 1.0.2 - Reading MaxHeapSize:
java -XX:+PrintFlagsFinal -version 2>/dev/null | awk '/ MaxHeapSize /{print $4}' - If you give a directory to HeapDumpPath, the file is created inside it as java_pid.hprof. Create the directory in advance.
- Common mistakes: putting the dump path somewhere that disappears, leaving a zombie without ExitOnOutOfMemoryError, and stopping after looking only at the top of the histogram ([B).
Create an OutOfMemoryError
Compile Leak.java in /root/jvm/heap, run it with -Xmx64m, and save the output to /root/jvm/heap/oom.log. It must contain java.lang.OutOfMemoryError: Java heap space.
After javac -d /root/jvm/heap /opt/lab/fixtures/jvm/Leak.java, run java -Xmx64m -cp /root/jvm/heap Leak > oom.log 2>&1. The heap fills within a few seconds.
A dump at the moment of the incident
Run it with -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/jvm/heap/oom.hprof added and save the output to /root/jvm/heap/oom-dump.log. There must be oom.hprof (1MB or more, starting with JAVA PROFILE 1.0.2) and Heap dump file created.
The two options come before the class name. The JVM creates the dump at the moment the exception is thrown — Dumping heap to ... is printed in the log.
Histogram and dump of a live process
Start Leak in the background with -Xmx64m -Dleak.sleep.ms=50 and after 2 seconds save jcmd GC.class_histogram > /root/jvm/heap/histo.txt and jcmd GC.heap_dump /root/jvm/heap/live.hprof.
After java ... & PID=$!, sleep 2 and call the two jcmd commands. heap_dump first requests a Full GC, so only reachable objects remain.
Read the first line of the histogram
From the first-ranked line in histo.txt, write the class name and instance count to /root/jvm/heap/top.txt as top_class= and instances=.
The first-ranked line starts with ' 1:' and the columns are rank, instances, bytes, and class name. The name is up to before (java.base@…). awk '$1=="1:"{print $2, $4}' gives the two values.
The same allocation, only without references
Run it with -Xmx64m -Dleak.retain=false -Dleak.iterations=3000 and save the output to /root/jvm/heap/noleak.log. It must contain done iterations=3000 and no OutOfMemoryError.
It allocates 3000 × 256KB = 750MB but runs to the end on a 64MB heap. That is because GC reclaims it the moment you let go.
Do not create a zombie
Run it with -XX:+ExitOnOutOfMemoryError added, and save the output to /root/jvm/heap/exit-on-oom.log and the exit code to /root/jvm/heap/exit.txt. It must contain Terminating due to java.lang.OutOfMemoryError and exit.txt must be 3.
java ... > exit-on-oom.log 2>&1; echo $? > exit.txt. At the first OutOfMemoryError the JVM ends immediately, and the exit code is 3 (measured).
Measure the container heap default
Write the MaxHeapSize of java -XX:+PrintFlagsFinal -version and the MaxHeapSize with -XX:MaxRAMPercentage=50 to /root/jvm/heap/ram.txt as default_max_heap= and half_max_heap=.
Extract the values with the awk in the Notes section, and for the second, put -XX:MaxRAMPercentage=50 before -XX:+PrintFlagsFinal. The default is 25%, so the second value is about double.
Nail the options down in a script
Create /root/jvm/heap/start.sh: it runs Leak with -Xmx64m, HeapDumpOnOutOfMemoryError, HeapDumpPath=/root/jvm/heap/dumps, ExitOnOutOfMemoryError, and -Xlog:gc:file=/root/jvm/heap/gc.log, and writes the exit code to /root/jvm/heap/runbook-exit.txt. When run, it creates a .hprof in dumps/, gc.log, and runbook-exit.txt (3).
If you give a directory to HeapDumpPath, the file is created as java_pid.hprof — put mkdir -p on the script's first line. The last line is echo $? > runbook-exit.txt.