基于本文回答
0
评论

Spark 的内存模型(Unified Memory Manager)

知识点图片

Apache Spark 的内存管理模型在 Spark 1.6 版本中经历了一次重大变革,从静态内存管理(Static Memory Manager)转变为统一内存管理(Unified Memory Manager)

统一内存管理(Unified Memory Manager)的核心目标是提高内存利用率,通过让“执行内存”和“存储内存”共享同一块区域,实现动态占用。

以下是关于 Spark 统一内存模型的详细解析:


1. 内存区域划分 (Memory Layout)

在一个 Executor 的 JVM Heap(堆内存)中,Spark 将内存划分为三个主要区域:

A. Reserved Memory (预留内存)

  • 大小:固定为 300MB (硬编码)。
  • 用途:用于 Spark 内部系统运行,防止 OOM。
  • 注意:如果 Executor 的堆内存小于 1.5 * 300MB = 450MB,Spark 会直接报错启动失败。

B. User Memory (用户内存)

  • 大小(Java Heap - Reserved Memory) * (1 - spark.memory.fraction)
  • 默认占比:约 40% (如果 spark.memory.fraction 为 0.6)。
  • 用途
    • 存储用户自定义的数据结构(如 HashMap)。
    • Spark 算子中 UDF 创建的对象。
    • RDD 依赖关系(Dependency)等元数据。
    • 这部分内存不受 Spark 内存管理器控制,完全由 JVM GC 管理。如果这部分爆了,会报 OOM。

C. Spark Memory (统一内存区域)

  • 大小(Java Heap - Reserved Memory) * spark.memory.fraction
  • 默认占比:约 60% (默认 spark.memory.fraction = 0.6)。
  • 用途:这是 Unified Memory Manager 核心管理的区域,进一步细分为 Storage(存储)Execution(执行)

2. 统一内存的核心机制:Execution vs Storage

在 Spark Memory 区域内,不再像旧版本那样有物理上的硬隔离,而是逻辑上的划分。

  • Storage Memory (存储内存):用于缓存 RDD 数据 (cache/persist)、广播变量 (Broadcast) 和 Unroll 数据。
  • Execution Memory (执行内存):用于 Shuffle、Join、Sort、Aggregation 等计算过程中的缓冲。

动态占用规则 (Dynamic Occupancy Rules) —— 重点

这是统一内存模型最精髓的地方,遵循以下“软边界”规则:

  1. 双方空闲时:Execution 和 Storage 都可以占用对方空闲的内存空间。
  2. Execution 借用 Storage
    • 如果 Execution 内存不足,它可以强制驱逐 (Evict) Storage 占用的内存块(前提是 Storage 占用的空间超过了 spark.memory.storageFraction 划定的基准线,或者 Storage 借用了 Execution 的空间)。
    • 被驱逐的 Storage 数据会根据持久化级别(StorageLevel)决定是丢弃还是写入磁盘。
  3. Storage 借用 Execution
    • 如果 Storage 内存不足,它可以借用 Execution 的空闲空间。
    • 但是!Storage 无法驱逐 Execution 的内存块
    • 原因:Execution 的数据通常是计算过程中的中间状态,如果被强制清理,会导致计算失败或需要极其昂贵的重算(Recompute)。

关键参数 spark.memory.storageFraction (默认 0.5)

虽然是动态共享,但 Spark 设定了一个“安全线”。

  • 这个参数定义了 Spark Memory 中不受驱逐的 Storage 内存区域
  • 默认 0.5 表示:只要 Storage 占用的内存不超过 Spark Memory 总量的 50%,Execution 就不能强行把这部分缓存踢出去。

3. 内存计算公式

假设设置 spark.executor.memory = 10GB,参数均为默认值:

  1. 系统可用内存10GB - 300MB (Reserved) ≈ 9.7GB
  2. Spark Unified Memory (60%)9.7GB * 0.6 = 5.82GB
    • 这 5.82GB 由 Execution 和 Storage 共享。
    • Storage 保护区 (50%)5.82GB * 0.5 = 2.91GB (这部分缓存只要存进去,Execution 抢不走)。
  3. User Memory (40%)9.7GB * 0.4 = 3.88GB

4. 堆外内存 (Off-Heap Memory)

除了堆内内存,Spark 也支持堆外内存(自 Spark 1.6 起引入,Spark 2.x+ 成熟)。

  • 开关spark.memory.offHeap.enabled = true
  • 大小spark.memory.offHeap.size
  • 特点
    • 直接在操作系统内存中分配,不受 JVM GC 扫描影响(减少 GC 停顿)。
    • 使用 sun.misc.Unsafe 进行操作。
    • 堆外内存同样遵循 统一内存管理模型,但它只有 Execution 和 Storage,没有 User Memory 和 Reserved Memory 的概念。

5. 为什么放弃静态内存管理 (Static Memory Manager)?

在 Spark 1.6 之前:

  • Execution 和 Storage 是硬隔离的(例如各占 50%)。
  • 痛点
    • 场景 A:你没有做任何 Cache,全是复杂的 Join。结果 Storage 区域空着,Execution 区域爆了(OOM 或频繁 Spill 到磁盘),导致性能极差。
    • 场景 B:你 Cache 了大量数据,但计算逻辑很简单。结果 Execution 空着,Storage 满了装不下更多缓存。
  • 统一内存管理的优势:谁忙谁多用,最大化利用物理内存,减少磁盘 I/O。

6. 调优建议

虽然默认配置适用于大多数场景,但在特定情况下需要调整:

  1. 增加 spark.memory.fraction (默认 0.6)
    • 如果你发现 JVM Heap 中有很多空闲,但 Spark 频繁 Spill(溢写磁盘)或清理缓存,且你的 User 代码(UDF、自定义对象)并没有占用太多内存,可以调大这个值(如 0.7 或 0.8)。
  2. 减小 spark.memory.fraction
    • 如果你频繁遇到 OutOfMemoryError: Java heap space,且报错位置在你的代码逻辑或大对象创建上(而不是 Spark 内部 Shuffle),说明 User Memory 不够用,需要减小该值给用户代码留空间。
  3. 调整 spark.memory.storageFraction (默认 0.5)
    • 重计算:如果你的任务主要是 ETL,几乎没有 Cache,可以调低这个值,让 Execution 更容易抢占内存。
    • 重缓存:如果你的任务是交互式查询,依赖大量 Cache 数据,且计算逻辑简单,可以调高这个值,保护缓存不被计算任务踢出。

总结图示

plaintext
+-----------------------------------------------------------------------+
|                          JVM Heap (Executor)                          |
+-----------------------------------------------------------------------+
|  Reserved  |                  Usable Memory                           |
|  (300MB)   |                                                          |
+------------+-----------------------------------+----------------------+
             |         Spark Memory (60%)        |   User Memory (40%)  |
             |      (Unified Memory Manager)     |                      |
             +-----------------------------------+----------------------+
             |         Dynamic Boundary          |                      |
             | <~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~> |                      |
             | Execution Memory | Storage Memory |                      |
             +------------------+----------------+                      |
             |                  | Protected Zone |                      |
             |                  | (storageFrac)  |                      |
             +------------------+----------------+----------------------+
右滑查看面试常问