---
# === IDENTITY ===
id: software/debugging/java-outofmemoryerror/2026
canonical_question: "How do I diagnose and fix Java OutOfMemoryError?"
aliases:
  - "java.lang.OutOfMemoryError Java heap space"
  - "Java OOM error fix"
  - "Java memory leak detection"
  - "Java heap dump analysis"
  - "GC overhead limit exceeded Java"
  - "Java OutOfMemoryError Metaspace"
  - "Java OutOfMemoryError unable to create new native thread"
  - "Java OutOfMemoryError direct buffer memory"
  - "jmap heap dump Java"
  - "Eclipse MAT memory leak"
entity_type: software_reference
domain: software > debugging > java_outofmemoryerror
region: global
jurisdiction: global
temporal_scope: 2000-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.95
version: 1.1
first_published: 2026-02-20

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Java 25 made ZGC generational-only — removed non-generational mode (Sep 2025)"
  next_review: 2026-11-13
  change_sensitivity: low

# === AGENT HINTS ===
inputs_needed:
  - key: "oom_type"
    question: "What is the exact OutOfMemoryError message? (e.g., Java heap space, Metaspace, GC overhead limit exceeded, unable to create new native thread, Direct buffer memory)"
    type: choice
    options: ["Java heap space", "GC overhead limit exceeded", "Metaspace", "Unable to create new native thread", "Direct buffer memory", "Requested array size exceeds VM limit", "Compressed class space", "Kill process or sacrifice child", "Unknown"]
  - key: "java_version"
    question: "Which Java version are you running? (java -version)"
    type: text
  - key: "container"
    question: "Is the application running in a container (Docker/Kubernetes)?"
    type: boolean

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/java-outofmemoryerror/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === CONSTRAINTS ===
constraints:
  - "JVM-specific (HotSpot, OpenJ9) — flags (-Xmx, -XX:+HeapDumpOnOutOfMemoryError) and tools (jmap, jcmd, Eclipse MAT) do not apply to Android ART/Dalvik without adaptation"
  - "Heap dump analysis requires a matching or later JDK version of Eclipse MAT — opening a JDK 21 heap dump in MAT built for JDK 11 may fail to parse"
  - "Increasing -Xmx is a band-aid, not a fix — always capture and analyze a heap dump before resizing the heap"
  - "Each OOM subtype (heap vs Metaspace vs native thread vs direct buffer) requires a different diagnostic path — do not apply heap-space fixes to non-heap OOM types"
  - "Container-based JVMs (Docker/K8s) may be killed by the Linux OOM Killer before the JVM throws OutOfMemoryError, producing no heap dump — check dmesg and exit code 137 first"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "The error is java.lang.StackOverflowError (infinite recursion, not memory exhaustion)"
    use_instead: "Increase -Xss or fix the recursive call — StackOverflowError is a stack depth issue, not a heap/memory issue"
  - condition: "Native/off-heap memory leak via JNI, Unsafe, or direct ByteBuffer outside JVM management"
    use_instead: "Use Native Memory Tracking (-XX:NativeMemoryTracking=detail), pmap, or jemalloc profiling — heap dump analysis will not show off-heap leaks"
  - condition: "Kubernetes pod killed with exit code 137 (OOMKilled) but JVM never threw OutOfMemoryError"
    use_instead: "Adjust container resources.limits.memory and resources.requests.memory — this is a container resource limit, not a JVM heap issue"
  - condition: "C/C++ segmentation fault or memory corruption (e.g., SIGSEGV in native code)"
    use_instead: "Use Valgrind, AddressSanitizer, or platform-specific native debugging tools — this is not a JVM error"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/debugging/java-classnotfoundexception/2026"
      label: "Java ClassNotFoundException (classloader / classpath issues)"
    - id: "software/debugging/nodejs-memory-leaks/2026"
      label: "Node.js memory leak diagnosis (same diagnostic loop, different runtime)"
    - id: "software/debugging/python-memory-leaks/2026"
      label: "Python memory leak diagnosis (same diagnostic loop, different runtime)"
  often_confused_with:
    - id: "software/debugging/python-recursion-stackoverflow/2026"
      label: "StackOverflowError equivalent (stack depth, not heap memory)"
    - id: "software/debugging/docker-oomkilled/2026"
      label: "Docker/Kubernetes OOMKilled (container memory limit, not JVM heap)"
  solves: []
  alternative_to: []

# === SOURCES (9 authoritative sources) ===
sources:
  - id: src1
    title: "Understand the OutOfMemoryError Exception"
    author: Oracle
    url: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks002.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src2
    title: "Java OutOfMemoryError Exception: Understanding 9 Types, Causes & Solutions"
    author: HeapHero
    url: https://blog.heaphero.io/types-of-outofmemoryerror/
    type: technical_blog
    published: 2024-06-01
    reliability: high
  - id: src3
    title: "Java OutOfMemoryError Exceptions: Causes & Fixes"
    author: Sematext
    url: https://sematext.com/blog/java-lang-outofmemoryerror/
    type: technical_blog
    published: 2025-03-19
    reliability: high
  - id: src4
    title: "How to Fix java.lang.OutOfMemoryError: Java Heap Space"
    author: Last9
    url: https://last9.io/blog/java-lang-outofmemoryerror/
    type: technical_blog
    published: 2025-01-01
    reliability: high
  - id: src5
    title: "How to Fix the OutOfMemoryError in Java"
    author: Rollbar
    url: https://rollbar.com/blog/how-to-handle-outofmemoryerror-exceptions-in-java/
    type: technical_blog
    published: 2024-06-01
    reliability: high
  - id: src6
    title: "Diagnose Leaks in Java Language Code"
    author: Oracle
    url: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks004.html
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src7
    title: "Troubleshooting OutOfMemoryError in Java"
    author: HowToDoInJava
    url: https://howtodoinjava.com/java/exception-handling/java-outofmemoryerror/
    type: technical_blog
    published: 2024-08-01
    reliability: high
  - id: src8
    title: "Java SE 25 — Troubleshoot Memory Leaks"
    author: Oracle
    url: https://docs.oracle.com/en/java/javase/25/troubleshoot/troubleshooting-memory-leaks.html
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src9
    title: "JVM Memory Issues in Production: Debugging Guide (OOM, GC, Leaks)"
    author: TheCodeForge
    url: https://thecodeforge.io/java/jvm-memory-debugging/
    type: technical_blog
    published: 2026-01-15
    reliability: high
---

# How Do I Diagnose and Fix Java OutOfMemoryError?

## TL;DR

- **Bottom line**: Java `OutOfMemoryError` has 8+ distinct types, each with different causes and fixes. The diagnosis loop is: **Identify** (which OOM type from the error message) -> **Capture** (heap dump with `-XX:+HeapDumpOnOutOfMemoryError`) -> **Analyze** (with Eclipse MAT or VisualVM) -> **Fix** (increase memory, fix leak, or tune GC). ~70% of OOM errors are "Java heap space" caused by memory leaks or undersized heaps.
- **Key tool/command**: `jmap -dump:format=b,file=heap.hprof <pid>` to capture a heap dump, then analyze with Eclipse MAT's Leak Suspects report.
- **Watch out for**: Increasing `-Xmx` without analyzing the heap dump just delays the crash — if there is a memory leak, you must find and fix the leaking code path.
- **Works with**: Java 8+ (all current LTS versions: 8, 11, 17, 21). Metaspace replaces PermGen from Java 8 onward.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Never catch `OutOfMemoryError` and continue normal operation — the JVM state may be corrupted after OOM. Only catch for graceful shutdown or logging. [src1]
- In containers (Docker/Kubernetes), always use `-XX:+UseContainerSupport` (default since JDK 10) and `-XX:MaxRAMPercentage=75.0` instead of fixed `-Xmx` values to respect container memory limits. [src4]
- Always enable `-XX:+HeapDumpOnOutOfMemoryError` in production — without a heap dump, diagnosis is guesswork. [src1, src6]
- Do not set `-Xmx` larger than 80% of available physical/container RAM — the JVM needs native memory for threads, Metaspace, direct buffers, JIT code cache, and GC overhead. [src2]
- If the error is "Kill process or sacrifice child" (Linux OOM Killer), the fix is at the OS/container level, not the JVM level. [src2]

## Quick Reference

| # | OOM Type | Error Message | Likelihood | Primary Cause | Fix |
|---|---|---|---|---|---|
| 1 | Java heap space | `OutOfMemoryError: Java heap space` | ~50% of cases | Memory leak or undersized `-Xmx` | Analyze heap dump; increase `-Xmx` or fix leak [src1] |
| 2 | GC overhead limit exceeded | `OutOfMemoryError: GC overhead limit exceeded` | ~20% | GC spending >98% of time, recovering <2% heap | Same as heap space — fix leak or increase heap [src1] |
| 3 | Metaspace | `OutOfMemoryError: Metaspace` | ~10% | Too many classes loaded (dynamic proxies, reflection) | Increase `-XX:MaxMetaspaceSize`; fix classloader leak [src1, src2] |
| 4 | Unable to create native thread | `OutOfMemoryError: Unable to create new native thread` | ~8% | Thread leak or OS thread limit reached | Fix thread leak; reduce `-Xss`; increase `ulimit -u` [src2] |
| 5 | Direct buffer memory | `OutOfMemoryError: Direct buffer memory` | ~5% | NIO direct buffers not released | Increase `-XX:MaxDirectMemorySize`; fix buffer leak [src2] |
| 6 | Requested array size | `OutOfMemoryError: Requested array size exceeds VM limit` | ~3% | Array allocation > `Integer.MAX_VALUE` or heap | Fix array sizing logic; process in chunks [src1] |
| 7 | Compressed class space | `OutOfMemoryError: Compressed class space` | ~2% | Class metadata exceeds compressed pointer space | Increase `-XX:CompressedClassSpaceSize` [src1] |
| 8 | Kill process or sacrifice child | `OutOfMemoryError: Kill process or sacrifice child` | ~2% | Linux OOM Killer terminated JVM | Increase container/host RAM; tune `oom_score_adj` [src2] |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (28 lines)

```
START — java.lang.OutOfMemoryError thrown
├── Error message contains "Java heap space" or "GC overhead limit exceeded"?
│   ├── YES → Heap problem
│   │   ├── Heap dump available? (HeapDumpOnOutOfMemoryError enabled?)
│   │   │   ├── YES → Open in Eclipse MAT → Leak Suspects report
# ... (see full script)
```

## Step-by-Step Guide

### 1. Enable heap dump on OOM (do this first, before you need it)

Configure the JVM to automatically capture a heap dump when any OutOfMemoryError occurs. This is the single most important diagnostic step. [src1, src6]

```bash
# Add to JVM startup options (JAVA_OPTS, application.properties, etc.)
java -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/var/log/java/heap-dump.hprof \
     -XX:+ExitOnOutOfMemoryError \
     -jar myapp.jar
```

**Verify**: `java -XX:+PrintFlagsFinal -version 2>&1 | grep HeapDumpOnOutOfMemoryError` -> expected: `bool HeapDumpOnOutOfMemoryError = true`

### 2. Identify the OOM type from the error message

The error message after `OutOfMemoryError:` tells you exactly which memory region is exhausted. [src1]

```bash
# Search application logs for the specific OOM type
grep -A 5 "OutOfMemoryError" /var/log/myapp/application.log

# Common patterns:
# "Java heap space"              → Heap (-Xmx)
# "GC overhead limit exceeded"   → Heap (-Xmx)
# "Metaspace"                    → Class metadata (-XX:MaxMetaspaceSize)
# "Unable to create new native thread" → Thread limit
# "Direct buffer memory"         → NIO buffers (-XX:MaxDirectMemorySize)
```

### 3. Capture a heap dump (if not auto-captured)

If `HeapDumpOnOutOfMemoryError` was not enabled, capture a dump from the running process before it crashes. [src6]

```bash
# Find Java process PID
jps -lv

# Capture heap dump (~2 sec per GB of used heap — causes brief pause)
jmap -dump:format=b,file=/tmp/heap.hprof <pid>

# Alternative: trigger via jcmd (preferred on modern JDKs)
jcmd <pid> GC.heap_dump /tmp/heap.hprof

# Quick histogram (no full dump, minimal impact)
jmap -histo <pid> | head -30
```

**Verify**: `ls -lh /tmp/heap.hprof` -> file size should be close to used heap

### 4. Analyze the heap dump with Eclipse MAT

Eclipse Memory Analyzer Tool (MAT) is the most effective free tool for finding memory leaks. [src6, src4]

```bash
# Download Eclipse MAT from https://eclipse.dev/mat/
# Open the .hprof file in MAT

# Key reports to check:
# 1. Leak Suspects Report (automatic) — highlights top memory consumers
# 2. Dominator Tree — shows objects retaining the most memory
# 3. Histogram — sorted by retained heap size
# 4. Path to GC Roots (exclude weak references) — shows WHY an object is retained
```

**Verify**: MAT's Leak Suspects report identifies 1-3 "Problem Suspect" entries with object types and retained sizes

### 5. Check GC behavior

Review garbage collection logs to understand memory pressure patterns. [src4]

```bash
# Enable GC logging (JDK 9+)
java -Xlog:gc*:file=/var/log/java/gc.log:time,uptime,level,tags \
     -jar myapp.jar

# Enable GC logging (JDK 8)
java -verbose:gc -Xloggc:/var/log/java/gc.log \
     -XX:+PrintGCDetails -XX:+PrintGCDateStamps \
     -jar myapp.jar

# Analyze with GCViewer or GCeasy.io
# Look for: Full GC frequency increasing, heap not returning to baseline after GC
```

### 6. Apply the fix based on OOM type

After identifying the root cause, apply the appropriate fix. [src1, src2, src4]

```bash
# Heap space / GC overhead — increase heap (temporary) while fixing leak
java -Xms2g -Xmx4g -jar myapp.jar

# Metaspace — increase class metadata space
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -jar myapp.jar

# Native threads — reduce stack size + increase OS limit
java -Xss512k -jar myapp.jar
ulimit -u 65536  # Linux: max user processes

# Direct buffers — increase NIO direct memory
java -XX:MaxDirectMemorySize=512m -jar myapp.jar

# Containers — use percentage-based sizing
java -XX:+UseContainerSupport \
     -XX:MaxRAMPercentage=75.0 \
     -XX:InitialRAMPercentage=50.0 \
     -jar myapp.jar
```

## Code Examples

### Java: detect and log memory pressure before OOM

> Full script: [java-detect-and-log-memory-pressure-before-oom.java](scripts/java-detect-and-log-memory-pressure-before-oom.java) (29 lines)

```java
// Input:  Running JVM with MemoryMXBean
// Output: Early warning logs when heap usage exceeds threshold
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
# ... (see full script)
```

### Bash: automated OOM diagnostic script

```bash
#!/bin/bash
# Input:  Java PID (or auto-detect)
# Output: Memory diagnostics: heap usage, thread count, top objects

PID="${1:-$(jps -lv | grep -v Jps | head -1 | awk '{print $1}')}"

if [ -z "$PID" ]; then
    echo "No Java process found"; exit 1
fi

echo "=== Java OOM Diagnostic Report — PID: $PID ==="
echo "--- JVM Flags ---"
jcmd "$PID" VM.flags 2>/dev/null || jinfo -flags "$PID"

echo ""
echo "--- Heap Usage ---"
jcmd "$PID" GC.heap_info 2>/dev/null || jmap -heap "$PID"

echo ""
echo "--- Top 20 Objects by Instance Count ---"
jmap -histo "$PID" | head -25

echo ""
echo "--- Thread Count ---"
jstack "$PID" 2>/dev/null | grep -c "^\"" || echo "N/A"

echo ""
echo "--- Native Memory (if NMT enabled) ---"
jcmd "$PID" VM.native_memory summary 2>/dev/null || echo "NMT not enabled. Start with: -XX:NativeMemoryTracking=summary"
```

### Java: common memory leak patterns to check

> Full script: [java-common-memory-leak-patterns-to-check.java](scripts/java-common-memory-leak-patterns-to-check.java) (32 lines)

```java
// Input:  Code review checklist for OOM prevention
// Output: Fixed patterns that prevent memory leaks
// PATTERN 1: Unbounded cache — use bounded Caffeine/Guava cache
// BAD:  static Map<String, Object> cache = new HashMap<>();
// GOOD:
# ... (see full script)
```

## Anti-Patterns

### Wrong: Catching OutOfMemoryError and continuing

```java
// BAD — JVM state may be corrupted after OOM; continuing is dangerous [src1]
try {
    byte[] data = new byte[Integer.MAX_VALUE];
} catch (OutOfMemoryError e) {
    System.out.println("Not enough memory, retrying with smaller size...");
    byte[] data = new byte[1024 * 1024]; // may work, may not — JVM is unreliable now
}
```

### Correct: Crash fast and analyze the heap dump

```java
// GOOD — let the JVM crash, capture dump, fix root cause [src1]
// JVM flags: -XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError
// The heap dump gives you the evidence to fix the actual problem.
// If you need graceful shutdown, use -XX:OnOutOfMemoryError="kill -9 %p"
```

### Wrong: Blindly increasing -Xmx without analysis

```bash
# BAD — just delays the crash if there is a memory leak [src4, src6]
# Monday:  java -Xmx2g -jar app.jar   # OOM after 6 hours
# Tuesday: java -Xmx4g -jar app.jar   # OOM after 12 hours
# Wednesday: java -Xmx8g -jar app.jar # OOM after 24 hours — still leaking!
```

### Correct: Analyze heap dump first, then right-size

```bash
# GOOD — capture evidence, then decide [src4, src6]
# 1. Enable dump: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/
# 2. Reproduce OOM
# 3. Open dump in Eclipse MAT → Leak Suspects
# 4. If leak found → fix code, keep original -Xmx
# 5. If no leak → increase -Xmx to match actual usage + 25% headroom
```

### Wrong: Using fixed -Xmx in containers

```bash
# BAD — ignores container memory limits; may get OOM-killed [src4]
# Container has 2GB limit, JVM thinks it has 16GB host RAM
docker run -m 2g myapp:latest java -Xmx8g -jar app.jar
# Result: Linux OOM Killer terminates JVM
```

### Correct: Use container-aware memory settings

```bash
# GOOD — JVM respects container memory limits [src4]
docker run -m 2g myapp:latest java \
    -XX:+UseContainerSupport \
    -XX:MaxRAMPercentage=75.0 \
    -jar app.jar
# JVM calculates: 2GB * 75% = 1.5GB max heap — leaves room for native memory
```

## Common Pitfalls

- **PermGen vs Metaspace confusion**: Java 7 and earlier use PermGen (`-XX:MaxPermSize`); Java 8+ uses Metaspace (`-XX:MaxMetaspaceSize`). Using the wrong flag is silently ignored. Check your Java version first. [src1, src2]
- **Heap dump file fills disk**: Heap dumps are roughly the size of used heap. A 16GB heap generates a 16GB dump file. Ensure `-XX:HeapDumpPath` points to a volume with sufficient free space. [src6]
- **Native Memory Tracking (NMT) overhead**: `-XX:NativeMemoryTracking=detail` adds 5-10% memory overhead. Use `summary` mode in production. [src1]
- **Thread stack size compounds**: Each thread uses `-Xss` memory (default 512KB-1MB). With 2000 threads, that is 1-2GB of native memory, invisible to heap metrics. [src2]
- **G1GC humongous allocations**: Objects larger than half a G1 region (default 16MB) are allocated as "humongous" objects and can cause premature OOM. Split large allocations or increase `-XX:G1HeapRegionSize`. [src4]
- **Finalize() methods delay GC**: Objects with `finalize()` require two GC cycles to be collected. In high-allocation scenarios, the finalizer queue can fill faster than it drains, causing OOM. Remove `finalize()` and use `try-with-resources` or `Cleaner` instead. [src1, src5]
- **String.intern() memory leak**: Aggressive use of `String.intern()` in Java 7+ stores strings in the heap (not PermGen), which can cause heap exhaustion if interning unbounded user input. [src3]

## Decision Logic

Use these if/then rules for agent-driven recommendations. Each rule maps a user-described situation to a concrete next step.

### If error message contains "Java heap space" or "GC overhead limit exceeded"
--> Capture a heap dump (`-XX:+HeapDumpOnOutOfMemoryError` or `jcmd <pid> GC.heap_dump`) and open it in Eclipse MAT. Run the Leak Suspects report first — if a single object/collection dominates the heap, fix the leak in code; if many objects share the heap, increase `-Xmx`. [src1, src6]

### If error message contains "Metaspace" and it grows on every redeploy
--> Diagnose as a classloader leak. Application servers (Tomcat, JBoss) often leak `ClassLoader` instances on hot redeploy. Restart the JVM, then audit static caches, thread locals, and JDBC driver registration. Increasing `-XX:MaxMetaspaceSize` only delays the crash. [src2]

### If error is "Unable to create new native thread" on JDK 21+
--> Check whether the workload should be using virtual threads (Project Loom) instead of platform threads. Virtual threads cost ~256 bytes vs ~1 MB for a platform thread, eliminating this class of OOM at scale. If platform threads are required, raise `ulimit -u` and reduce `-Xss` to 512k. [src9]

### If error is "Direct buffer memory" in a Netty or Spring WebFlux application
--> Investigate `PooledByteBufAllocator` retention — the most common cause is `ByteBuf` instances escaping the request scope without `ReferenceCountUtil.release()`. Enable NMT (`-XX:NativeMemoryTracking=summary`) and run `jcmd <pid> VM.native_memory summary` to confirm direct memory growth. [src2, src9]

### If running in Docker/Kubernetes and the container restarts with exit code 137
--> This is OS-level OOMKilled, not a JVM OOM — no heap dump will be generated. Set container memory to ~1.43× the desired max heap (off-heap accounts for ~30% of OOMs). Configure the JVM with `-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0`. Set `resources.requests.memory` close to `resources.limits.memory`. [src4, src9]

### If on Java 25+ and using ZGC
--> ZGC is generational-only in Java 25 (non-generational mode removed). Use `-XX:+UseZGC` (no longer need `-XX:+ZGenerational`). For OOM diagnosis, enable JFR (`-XX:StartFlightRecording=settings=default,maxsize=100M,maxage=1h`) — sub-2% overhead and captures allocation hotspots without a full heap dump. Ensure the heap has enough headroom for the live set plus concurrent-GC working space. [src8]

### If you need low-overhead production profiling instead of heap dumps
--> Use async-profiler (`-e alloc` for allocation profiling) or JFR. Both have <2% CPU overhead and surface allocation hotspots over time. Heap dumps remain authoritative for post-mortem leak analysis, but JFR/async-profiler are now the preferred live-investigation tools. [src9]

## Diagnostic Commands

```bash
# === Identify the Java process ===
jps -lv                              # list Java processes with JVM flags
jcmd -l                              # alternative (modern JDKs)

# === Heap overview ===
jcmd <pid> GC.heap_info              # current heap usage (JDK 9+)
jmap -heap <pid>                     # heap summary with GC algorithm (JDK 8)

# === Heap dump ===
jcmd <pid> GC.heap_dump /tmp/heap.hprof   # recommended (JDK 9+)
jmap -dump:format=b,file=/tmp/heap.hprof <pid>  # JDK 8+

# === Object histogram (quick, no full dump) ===
jmap -histo <pid> | head -30         # top objects by instance count
jmap -histo:live <pid> | head -30    # forces GC first, then histogram

# === Thread analysis ===
jstack <pid> > /tmp/threads.txt      # full thread dump
jstack <pid> | grep -c "^\"" | head  # thread count
jcmd <pid> Thread.print              # alternative

# === GC behavior ===
jstat -gcutil <pid> 1000 10          # GC stats every 1s for 10 iterations
# Columns: S0% S1% E% O% M% — shows Eden, Old, Metaspace utilization

# === Native memory tracking (must start JVM with -XX:NativeMemoryTracking=summary) ===
jcmd <pid> VM.native_memory summary  # heap, metaspace, threads, code cache breakdown
jcmd <pid> VM.native_memory baseline # capture baseline for later diff
jcmd <pid> VM.native_memory detail.diff   # show change since baseline (leak hunt)

# === JFR (Java Flight Recorder) — production-safe alternative to heap dump ===
# Start JVM: -XX:StartFlightRecording=settings=default,maxsize=100M,maxage=1h
jcmd <pid> JFR.start name=oom-investigation duration=300s filename=/tmp/oom.jfr
jcmd <pid> JFR.dump name=oom-investigation filename=/tmp/oom.jfr
# Analyze with: jfr print --events jdk.ObjectAllocationInNewTLAB /tmp/oom.jfr
# Or open in JDK Mission Control (JMC)

# === async-profiler (low-overhead allocation profiling) ===
# Attach to running JVM, profile allocations for 60s:
asprof -e alloc -d 60 -f /tmp/alloc.html <pid>

# === JVM flags check ===
jcmd <pid> VM.flags                  # all active JVM flags
jinfo -flags <pid>                   # alternative
```

## Version History & Compatibility

| Java Version | Memory Model Change | Key Flags |
|---|---|---|
| Java 7 and earlier | PermGen for class metadata | `-XX:MaxPermSize=256m` |
| Java 8 | PermGen replaced by Metaspace (native memory) | `-XX:MaxMetaspaceSize=256m` |
| Java 9 | Unified GC logging (`-Xlog:gc*`) | `-Xlog:gc*:file=gc.log` replaces `-verbose:gc` |
| Java 10 | Container support (`UseContainerSupport` default on) | `-XX:MaxRAMPercentage=75.0` |
| Java 11 (LTS) | ZGC experimental; Epsilon GC (no-op) | `-XX:+UseZGC` (experimental) |
| Java 14 | `jmap -histo` includes lambda/hidden classes | — |
| Java 15 | ZGC production-ready | `-XX:+UseZGC` |
| Java 17 (LTS) | Strongly encapsulated JDK internals | `--add-opens` for reflection |
| Java 21 (LTS) | Virtual threads (Project Loom) reduce native thread OOM | `-XX:+UseZGC -XX:+ZGenerational` |
| Java 25 (LTS) | ZGC is generational-only; non-generational mode removed | `-XX:+UseZGC` (always generational) |

## When to Use / When Not to Use

| Use This Guide When | Don't Use When | Use Instead |
|---|---|---|
| `java.lang.OutOfMemoryError` in logs | `java.lang.StackOverflowError` | Increase `-Xss` or fix recursion |
| JVM process killed by OOM Killer (`dmesg | grep oom`) | Slow GC pauses but no OOM | GC tuning guide (G1GC/ZGC) |
| Heap usage grows monotonically over time | High CPU from GC but heap stable | GC algorithm selection guide |
| Application crashes after running for hours/days | Immediate crash on startup | Check classpath / dependency issues |
| Container restarts with exit code 137 | Container restarts with exit code 1 | Application error logs |

## Important Caveats

- **Heap dump captures a moment in time**: The dump only shows objects alive at the instant of capture. Transient allocations (short-lived bursts) may not appear. Use GC logs + heap dump together for a complete picture. [src6]
- **Eclipse MAT vs VisualVM**: MAT is far more powerful for leak analysis (Leak Suspects, dominator tree, OQL queries). VisualVM is better for live monitoring. Use MAT for post-mortem, VisualVM for live investigation. [src4, src6]
- **Container memory != JVM memory**: A 4GB container must accommodate JVM heap + Metaspace + threads + direct buffers + code cache + GC overhead. Rule of thumb: set `-Xmx` or `MaxRAMPercentage` to 75% of container memory limit. [src4]
- **ZGC and Shenandoah change OOM behavior**: Low-latency collectors may delay OOM errors but do not prevent them. The heap dump is still the primary diagnostic tool. [src2]
- **Kubernetes OOMKilled (exit 137) vs Java OOM**: If the pod is killed by Kubernetes before the JVM throws `OutOfMemoryError`, no heap dump is generated. Set `resources.requests.memory` close to `resources.limits.memory` and use `-XX:MaxRAMPercentage` to prevent this. [src4]
- **Off-heap memory causes ~30% of production OOMs**: Heap dumps will not show off-heap allocations (NIO direct buffers, Netty pooled allocators, thread stacks, Metaspace, JIT code cache, native libraries). Rule of thumb: set container memory to ~1.43× the desired max heap. Always confirm with NMT (`jcmd <pid> VM.native_memory summary`). [src9]
- **JFR vs heap dump trade-off**: JFR (with `-XX:StartFlightRecording=settings=default,maxsize=100M,maxage=1h`) has <2% CPU overhead and captures allocation hotspots continuously. Heap dumps are still authoritative for post-mortem leak analysis, but JFR is now the preferred live-investigation tool. [src8, src9]

## Related Units

- [Java ClassNotFoundException](/software/debugging/java-classnotfoundexception/2026)
- [Node.js memory leaks](/software/debugging/nodejs-memory-leaks/2026)
- [Python memory leaks](/software/debugging/python-memory-leaks/2026)
- [Docker OOMKilled (exit 137)](/software/debugging/docker-oomkilled/2026)
