Jenkins Error: 'java.lang.OutOfMemoryError: Java heap space' — Cause, Fix, and Troubleshooting Guide
Fix 'java.lang.OutOfMemoryError: Java heap space' in Jenkins: size the controller/agent JVM heap, enable heap dumps, tune G1GC, and trim fat builds.
- #jenkins
- #ci-cd
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
The Jenkins Java Virtual Machine has exhausted its heap: it cannot allocate any more objects and the garbage collector can no longer reclaim enough space to continue. On the controller this typically freezes the UI, stalls the build queue, and eventually crashes the process; on an agent it kills the remoting JVM and drops the build.
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3745)
at hudson.remoting....
at org.jenkinsci.plugins.workflow...
Exception in thread "..." java.lang.OutOfMemoryError: Java heap space
The key question is which JVM ran out of memory — the controller or an agent — because the controller and every agent are separate Java processes with separate heaps. The error is a symptom of the max heap (-Xmx) being too small for the workload, or of the workload leaking/retaining far more than it should (giant console logs, thousands of build records, fat pipeline global variables).
Symptoms
- The Jenkins web UI becomes unresponsive or returns 500s; the process may exit shortly after.
- Builds hang, then fail with
OutOfMemoryError: Java heap spacein the build console or agent log. journalctl -u jenkins(or the container log) shows repeatedOutOfMemoryErrorand long GC pauses.- A heap dump file (
java_pid<PID>.hprof) appears in the Jenkins home or working directory. - CPU spikes to 100% on one core for extended periods (the GC thrashing) before the crash.
- The failure correlates with a specific job that produces huge console output or archives large artifacts.
Common Root Causes
- Heap ceiling too low for the workload — the default or configured
-Xmxis smaller than the controller/agent actually needs for the number of jobs, plugins, and concurrent builds. - Too many builds / oversized build history — thousands of retained builds keep large
Runobjects and flow node data resident; the controller holds much of it in memory. - Massive console logs — a step that prints megabytes per second (verbose test output,
set -xon a huge script) buffers enormous strings on the controller. - Fat pipeline global variables / retained objects — large collections, parsed JSON/XML, or file contents held in pipeline script scope multiply across concurrent builds.
- Plugin or pipeline leak — a plugin retains references (listeners, caches) that never get released, so the heap grows build over build until exhaustion.
- Confusing controller vs agent heap — you raised
-Xmxon the controller but the OOM is actually on the agent JVM (or vice versa), so the fix never lands on the right process.
How to diagnose
First determine which JVM died. The stack trace location and the log file tell you: a controller trace comes from the Jenkins service log, an agent trace from the agent/remoting log or the node’s log page under Manage Jenkins → Nodes → agent-01 → Log.
Check the controller service log and its current heap settings:
# Controller JVM: what is it actually running with?
journalctl -u jenkins --since '1 hour ago' | grep -i 'OutOfMemoryError\|heap'
ps -o pid,rss,args -p "$(pgrep -f 'jenkins.war' | head -1)" | tr ' ' '\n' | grep -i 'Xmx\|Xms'
Inspect live memory from the Jenkins Script Console (Manage Jenkins → Script Console). This runs on the controller JVM:
def rt = Runtime.runtime
long mb = 1024 * 1024
println "max: ${rt.maxMemory() / mb} MB" // -Xmx
println "total: ${rt.totalMemory() / mb} MB" // committed
println "free: ${rt.freeMemory() / mb} MB"
println "used: ${(rt.totalMemory() - rt.freeMemory()) / mb} MB"
Find the jobs most likely to be responsible — the ones retaining the most build history or producing the largest logs:
// Largest build.xml / log footprints by job (Script Console)
Jenkins.instance.getAllItems(hudson.model.Job).collect { j ->
[j.fullName, j.builds.size()]
}.sort { -it[1] }.take(15).each { println "${it[1]}\t${it[0]}" }
When a dump exists, analyze it offline (Eclipse MAT or jhat) to see the dominator tree — usually Run/FlowNode objects, byte[] console buffers, or a plugin cache:
# Confirm dump was produced and inspect a quick histogram
ls -lh $JENKINS_HOME/*.hprof
jmap -histo:live "$(pgrep -f jenkins.war | head -1)" | head -30
Fixes
1. Size the heap explicitly (do not rely on defaults)
Set -Xms equal to -Xmx so the heap does not resize under load, and pick a size that fits the host with room for OS/off-heap. For a package install, edit /etc/default/jenkins (Debian) or /etc/sysconfig/jenkins (RHEL) via JAVA_ARGS/JENKINS_JAVA_OPTIONS:
# /etc/default/jenkins (controller)
JAVA_ARGS="-Xms4g -Xmx4g"
For a systemd unit or container, set it through the environment:
# /etc/systemd/system/jenkins.service.d/override.conf
[Service]
Environment="JENKINS_JAVA_OPTIONS=-Xms4g -Xmx4g"
# Docker
docker run -e JAVA_OPTS="-Xms4g -Xmx4g" jenkins/jenkins:lts
Reload and restart after changing: sudo systemctl daemon-reload && sudo systemctl restart jenkins.
2. Turn on heap dumps and modern GC
Always capture a dump on OOM so you can find the culprit instead of guessing. G1GC (the default on modern JDKs) handles large heaps well:
JENKINS_JAVA_OPTIONS="-Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/jenkins/heapdump.hprof \
-XX:+ExitOnOutOfMemoryError"
-XX:+ExitOnOutOfMemoryError makes the process die cleanly (so systemd restarts it) rather than limping along in a half-dead state.
3. Fix the agent JVM, not just the controller
Agent heap is configured separately. For an inbound (JNLP) agent, pass JVM options to the agent process, or set them on the node under Configure → Advanced → JVM options for SSH agents:
# Inbound agent launch
java -Xmx2g -XX:+UseG1GC -jar agent.jar -jnlpUrl https://jenkins.example.com/computer/agent-01/jenkins-agent.jnlp
Note that -Xmx on the agent controls the remoting JVM, not build subprocesses — a mvn/gradle/node process forked by sh has its own memory and is not covered by the agent heap.
4. Trim what the controller retains
Cap build history and rotate logs so old runs release memory and disk. In a declarative pipeline:
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '30', artifactNumToKeepStr: '10'))
disableConcurrentBuilds()
}
stages {
stage('Build') { steps { sh 'make build' } }
}
}
5. Stop pipelines from holding large objects across steps
Do not keep whole files, parsed payloads, or big collections in pipeline scope. Stream to disk and process in a subprocess instead:
// BAD: pulls the entire file into the controller heap and keeps it in scope
def data = readFile('huge-report.json')
// BETTER: let a subprocess on the agent do the heavy lifting
sh 'jq ".summary" huge-report.json > summary.json'
def summary = readFile('summary.json').trim()
If a step genuinely floods the console, redirect verbose output to a file and archive it rather than printing it.
What to watch out for
- Never set
-Xmxabove what the host can back with real RAM — swapping a JVM heap causes catastrophic GC pauses that look like a different failure. - Leave headroom: heap is not the whole process. Metaspace, thread stacks, and off-heap remoting buffers live outside
-Xmx; size the host for heap plus roughly 25–50% overhead. - Confirm your change actually took effect — grep the running process args after restart; a stray second config file can silently override you.
- Watch for slow leaks: if OOM recurs a fixed number of builds after every restart, it is a leak, not an undersized heap. Bisect plugins and capture a dump.
- Raising heap on the controller will not help an agent-side OOM. Read the stack trace location first.
- For recurring incidents, the free incident assistant can help triage the crash log, and the Jenkins guides hub collects related fixes.
Related
- Jenkins Error: hudson.remoting.ChannelClosedException — an agent OOM often surfaces first as a dropped remoting channel.
- Jenkins Error: java.lang.StackOverflowError — the other common JVM memory failure, driven by recursion rather than heap size.
- GitLab job OOM-killed (exit 137) — the equivalent memory-exhaustion failure on GitLab runners.
More Jenkins prompts & error guides
Every Jenkins AI prompt and troubleshooting guide, in one place.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.