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.
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 see | Which process ran out | First thing to change |
|---|---|---|
| java.lang.OutOfMemoryError: Java heap space in an executor log | The executor JVM heap, while a task held its working set | More shuffle partitions, then check for skew, then executor memory |
| java.lang.OutOfMemoryError: Java heap space in the driver log | The driver JVM heap, usually after collect or a large broadcast | Return less to the driver, then spark.driver.memory |
| GC overhead limit exceeded | A heap so full that the JVM spends nearly all its time collecting | Same as Java heap space, plus fewer objects per row |
| Total size of serialized results ... is bigger than spark.driver.maxResultSize | Nothing yet: the driver aborted the job to protect itself | Write results to storage instead of collecting them |
| Container killed by YARN for exceeding physical memory limits | The container, outside the heap | spark.executor.memoryOverhead |
| ExecutorLostFailure and exit code 137 on Kubernetes | Usually the pod, killed by a SIGKILL | Memory 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.


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.
Know Spark out of memory error the way the interviewer who asks it knows it.
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
- 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.
- 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.
- 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.
- 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.
- 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?+
What does executor lost mean in Spark?+
Should I just raise spark.executor.memory?+
What is spark.executor.memoryOverhead for?+
Why does my Spark job run out of memory only on some tasks?+
The candidate who gets the offer
- 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
- 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
- 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
Spark optimization interview questions
Questions on partitioning and skew, plus caching and shuffles
Spark Streaming vs Structured Streaming
How the two Spark streaming APIs differ and which to use
PySpark interview questions
Questions data engineers are asked on Spark
PySpark practice problems
Hands-on problems with shuffles and joins
PySpark groupBy
Shuffle cost and skew in aggregations
Data engineer interview prep
The full plan for the interview loop
The weekly data challenge
Dirty, production-shaped data, scored blind each week.