0%

Spark 内存管理与 GC 调优

Spark 内存管理与 GC 调优

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

1. Executor 内存模型

参考: Spark 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 压力,但需手动设上限。

Spark JVM On-heap 内存分配
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