Spark 内存管理与 GC 调优
一句话定位:Executor 内存怎么分、谁来借、GC 为什么抖,是 Spark 性能调优的地基;理解内存模型才能解释”为什么缓存一多就 Full GC”和”为什么分区太小反而更慢”。
1. Executor 内存模型

Executor 的 JVM 堆被划分为(以 spark.memory.fraction 默认 0.6 为例,扣除 300MB Reserved 之后):
- 统一内存(Unified Memory):执行内存(Execution,排序/聚合/Join 的 shuffle 缓冲)与存储内存(Storage,缓存的 RDD/广播变量)共享一个池,默认各占 50%(
spark.memory.storageFraction)。- 动态借用:执行内存不足时可向存储内存借空间(逐出缓存的 block);但存储内存不能强行逐出正在被使用的执行内存。即执行内存优先,这也是”缓存过多反而拖慢计算”的根因——存储占了统一池,执行只能被挤压或 spill 到磁盘。
- 用户内存(User Memory):算子/UDF 自身对象、数据结构,不受 Spark 管理。
- 堆外内存(Off-Heap):
spark.memory.offHeap.enabled=true+spark.memory.offHeap.size;Tungsten 的二进制格式(排序/聚合/缓存)可直接写在堆外,由 OS 管理、不受 JVM GC 管辖,显著降低 GC 压力,但需手动设上限。

JVM On-heap 内存分配
2. GC 频繁的典型成因
- 分区过小 → 单分区对象少但分区数爆炸 → Young GC 次数飙升;
- 缓存过量(RDD/广播变量/大表缓存)→ Old Gen 持续高位 → Full GC;
- 大批量/大对象(如超大
collect、宽行)→ 大对象直入 Old Gen,触发提前 Full GC; - 堆外未开 + 统一内存被存储挤占 → 执行内存 spill 到磁盘,反而增加对象生命周期。
3. 调优手段
- 调分区数:
spark.sql.shuffle.partitions/repartition,让单任务数据量适中,减少对象数与 GC 频率。 - 控制缓存:非必要不
cache;用MEMORY_AND_DISK而非纯内存;及时unpersist。 - 开堆外:大数据量排序/聚合场景开启 Off-Heap,把 GC 压力移出 JVM。
- GC 器与参数:G1GC(
-XX:+UseG1GC)配合合理-Xmn/-XX:InitiatingHeapOccupancyPercent;监控 GC 日志与 Spark UI Executors 页的GC Time。 - 加资源:在单任务数据量确实大时,增大
executor.memory或降executor.cores(减少同进程并发对象)。
参考:Apache Spark 官方文档 Memory Management Overview / Tuning Guide