Spark Out of Memory Error

Java Heap Space, Executor Lost

A Spark out of memory error is 1 of 4 failures: Java heap space in an executor, Java heap space in the driver, a container killed for exceeding its memory limit, or an executor lost with no exception. Read the message to tell which, and fix the partitions or the overhead before you raise executor memory; if it's the driver, fix what the job returns to it.

Last updated: Proudly published by: Jeff Wahl7 min read

Which Spark out of memory error do you have?

People call 4 different Spark failures an out of memory error, and each one has a different fix. Find the line in the log that names the failure: a java.lang.OutOfMemoryError from the driver, the same from an executor, a container killed by the cluster manager, or ExecutorLostFailure with no exception at all.

The mistake that wastes the most time is raising spark.executor.memory for all 4. That setting changes the executor heap and, with it, the container size. It cannot fix a driver that collected too much, and it hides a skewed partition rather than curing it. Read the message first, then size memory.

Spark memory error messages and what each one means

What you seeWhich process ran outFirst thing to change
java.lang.OutOfMemoryError: Java heap space in an executor logThe executor JVM heap, while a task held its working setMore shuffle partitions, then check for skew, then executor memory
java.lang.OutOfMemoryError: Java heap space in the driver logThe driver JVM heap, usually after collect or a large broadcastReturn less to the driver, then spark.driver.memory
GC overhead limit exceededA heap so full that the JVM spends nearly all its time collectingSame as Java heap space, plus fewer objects per row
Total size of serialized results ... is bigger than spark.driver.maxResultSizeNothing yet: the driver aborted the job to protect itselfWrite results to storage instead of collecting them
Container killed by YARN for exceeding physical memory limitsThe container, outside the heapspark.executor.memoryOverhead
ExecutorLostFailure and exit code 137 on KubernetesUsually the pod, killed by a SIGKILLMemory overhead, then find what grew outside the heap

How executor memory is divided

Set spark.executor.memory to 8192 MiB and the executor doesn't get 8192 MiB of working room. The Spark tuning guide reserves 300 MiB, then gives the unified region 60% of what's left, and execution and storage share it. That is 0.6 x (8192 - 300) = 4735 MiB. The remaining 3157 MiB is for user data structures and internal metadata, and it also guards against OOM errors on sparse, unusually large records.

Execution means shuffles and aggregations, plus the joins and sorts built on them. Storage means cached data. Execution can evict cached blocks, but only down to the storage fraction (0.5 of the unified region, 2368 MiB here), and storage can never evict execution. With 4 cores running tasks at once, that region averages about 1184 MiB per task.

One executor container of 9011 MiB as a single bar: a 300 MiB reserved slice, 4735 MiB of execution and storage memory, 3157 MiB of user memory, together the 8192 MiB heap, then 819 MiB of overhead outside the heapOne executor container of 9011 MiB as a single bar: a 300 MiB reserved slice, 4735 MiB of execution and storage memory, 3157 MiB of user memory, together the 8192 MiB heap, then 819 MiB of overhead outside the heap

Outside the heap sits the overhead: by default 10% of executor memory with a 384 MiB minimum, 819 MiB here, so the container asked of the cluster is 9011 MiB. The heap and the container are 2 separate limits, and which one you hit decides the message you see.

Prepare for the interview
01 / Open invite
02min.

Know Spark out of memory error the way the interviewer who asks it knows it.

a Spark out of memory error query, the same shape a screen would give you.
The diff against expected. Where ties broke. What you missed.
sandbox
1SELECT user_id,
2 COUNT(*) AS sessions
3FROM events
4WHERE ts >= NOW() - INTERVAL '7 day'
5
Execute your solution0.4s avg.

Java heap space in an executor: too much data in 1 task

The JVM documentation says the Java heap space message means an object could not be allocated in the heap, and that it doesn't necessarily imply a leak. In Spark the usual reason is that 1 task needed more memory than its share. Spark's tuning guide describes it: the working set of a task, such as a reduce task in groupByKey, was too large, because shuffle operations build a hash table inside each task.

The simplest fix, in the same guide, is more parallelism so each task's input is smaller. The defaults make the arithmetic concrete. A 100 GiB shuffle split into the default 200 partitions puts 512 MiB in each, and in-memory objects take more room than the bytes on disk (the guide says a decompressed block is often 2 or 3 times its size). At the 64 MiB advisory size that adaptive query execution targets, the same shuffle is 1,600 partitions. The guide recommends 2 to 3 tasks per CPU core, and notes Spark runs tasks as short as 200 ms efficiently, so many small tasks cost little.

Adaptive query execution has been on by default since 3.2.0, according to the SQL tuning documentation, and it coalesces small shuffle partitions and splits skewed ones. A join partition counts as skewed when it's over 5 times the median partition and over 256 MiB. If 1 task fails while its siblings finish, compare its input size with the median in the Spark UI: a gap that size is skew, and no executor size fixes it for long. The PySpark groupBy guide covers the shuffle and skew patterns.

GC overhead limit exceeded is the same condition seen from the collector. The JVM throws it after spending about 98% of its time collecting while recovering less than 2% of the heap, for 5 consecutive collections. Treat it as a heap that is full of live data, and fix the data per task before reaching for JVM flags. The tuning guide adds that collection cost grows with the number of Java objects, so serialized caching is the first thing to try when GC is the problem.

Driver out of memory: collect, results and broadcasts

The driver holds the query plan and the task bookkeeping, and it receives everything an action returns. Its default heap is 1g. In client mode you cannot raise it through SparkConf in your application, because the driver JVM has already started; give it on the command line as the driver memory option of spark-submit or set it in the properties file.

collect and toPandas bring every partition's result to 1 JVM, and so do similar actions. spark.driver.maxResultSize (default 1g) caps the total serialized size of results, and a job over the limit is aborted with a message that starts "Total size of serialized results of N tasks". That abort is the limit working, not a crash. The documentation warns that a high limit may cause out-of-memory errors in the driver, so raising it moves the failure rather than removing it.

The better fix is to stop returning the data: aggregate first, write the result to storage and read a sample, or call limit before collect. Broadcasts need the same care. A table under spark.sql.autoBroadcastJoinThreshold (10 MiB by default, -1 to disable) is sent to all worker nodes, so raising the threshold spends memory on every executor heap, not only the driver.

Executor lost and container killed: memory outside the heap

ExecutorLostFailure means the driver lost an executor and the tasks on it failed. It names no cause, so the cause is in the executor's log or the cluster manager's diagnostic. The most common memory cause is a container killed for going past its limit, and it leaves no OutOfMemoryError in the executor log because the process was killed from outside.

On YARN, Spark's source builds the message Container killed by YARN for exceeding physical memory limits, followed by the sizes and a suggestion to boost spark.executor.memoryOverhead. A virtual memory variant also names yarn.nodemanager.vmem-pmem-ratio and yarn.nodemanager.vmem-check-enabled. On Kubernetes the pod memory request and limit are the sum of the memory and overhead settings, and Spark's documentation describes exit code 137 as a SIGKILL that may be a container OOM, with exit code 52 for a JVM OOM.

The container limit is the sum of 4 settings: spark.executor.memory and spark.executor.memoryOverhead, then spark.memory.offHeap.size and spark.executor.pyspark.memory. If you do not set the PySpark memory, Python workers count against the overhead, and Spark doesn't limit their use. That's why PySpark jobs hit container kills more often: the default overhead is sized for a JVM. Spark's own default overhead factor for non-JVM jobs on Kubernetes is 0.40 for this reason, because such tasks commonly fail with "Memory Overhead Exceeded" errors. Set spark.executor.memoryOverhead directly and the factor is ignored.

Not every lost executor is a memory problem. An executor that stops answering for longer than spark.network.timeout (120s by default) is removed, and heartbeats go out every 10s. A node failure or a preemption also loses executors. Spark source separates these cases: a loss unrelated to the task is not counted toward the 4 failures (spark.task.maxFailures) that fail a job, but a memory kill by YARN is treated as the application's fault and counts. Repeated executor loss is limited too, by spark.executor.maxNumFailures, which the configuration documentation says takes effect on YARN and Kubernetes.

How to diagnose a Spark out of memory error in order

  1. 01

    Find which process failed

    Read the driver log for the stage that failed, then the log of the executor named in ExecutorLostFailure. Driver heap and executor heap failures leave different lines, and a container kill leaves a third.

  2. 02

    Compare the failed task with the median

    In the Spark UI, set the largest task's input or shuffle read beside the median. A gap of 5 times or more is skew, so fix the data distribution before adding memory.

  3. 03

    Raise partitions before heap

    Increase spark.sql.shuffle.partitions or repartition so that each task's input is smaller, and confirm adaptive query execution is on.

  4. 04

    Size the overhead when the container was killed

    Set spark.executor.memoryOverhead directly, and spark.executor.pyspark.memory when Python workers do real work.

  5. 05

    Raise the heap last

    Increase spark.executor.memory only when the working set per task is still too large after the earlier steps, and keep spark.memory.fraction at its default.

Spark memory questions in a data engineering interview

Interviewers ask this as a diagnosis: a job fails on 1 task out of 2,000, what do you check and in what order. The strong answer separates heap from container, reads the message, finds skew by comparing the failed task with the median, and raises partitions before memory. Practice saying that sequence aloud with the PySpark interview questions, then work the PySpark practice problems that ask for shuffle and skew handling.

The same reasoning shows up in tuning questions, covered by the Spark optimization interview questions, and in the wider data engineer interview prep plan. Those questions expect you to say why a setting helps, not just what it is called.

Spark out of memory FAQ

What does java.lang.OutOfMemoryError: Java heap space mean in Spark?+
An object could not be allocated in the heap of the JVM that threw it, which is either an executor or the driver. The log line says which process it came from. Executor heap errors usually mean a task's working set is too large, so more partitions help first. Driver heap errors usually mean too much data was pulled back with collect or a similar action.
What does executor lost mean in Spark?+
The driver stopped hearing from an executor and removed it, and the tasks that were running on it failed with ExecutorLostFailure. The cause is in the executor's own log or the cluster manager's diagnostic: a container killed for exceeding memory limits, a JVM that exited, a node that went away or an executor that stopped answering past spark.network.timeout.
Should I just raise spark.executor.memory?+
Only after you know which memory ran out. Raising it grows the heap and the container together, so it helps a heap error and can help a container kill, but it does nothing for a driver error and it hides data skew, where 1 partition is far larger than the rest. More partitions or a fix for skew usually costs less.
What is spark.executor.memoryOverhead for?+
It's memory outside the JVM heap that the container needs for VM overheads, interned strings, native allocations and, in PySpark, the Python workers. The container limit is the sum of the heap, the overhead, any off-heap size and any PySpark memory, so a container can be killed while the heap is healthy.
Why does my Spark job run out of memory only on some tasks?+
Data skew. A task reads one partition, and a hot key puts far more rows in one partition than in the others, so that task needs far more memory than the median. Adaptive query execution can split skewed join partitions, and salting a hot key by hand works when it cannot.
02 / Why practice

The candidate who gets the offer

  1. 01

    Reading a solution is not the same as writing one

    Every engineer who has frozen on a query they had read a dozen times knows the gap. The only preparation that closes it is producing the answer yourself, under time, before the interview does it for you

  2. 02

    76% of hiring managers reject on the coding task, not the resume

    From HackerRank's 2024 Developer Skills Report. Candidates who look strong on paper still fail the live screen if they haven't done timed, executable practice

  3. 03

    Spark rounds are diagnosis rounds

    Skew, shuffles, broadcast judgment, partition pruning, exactly-once streaming. Reading the task table and naming the failure class is the signal, not the API

Related guides