行业资讯

Spark 核心之 Spark 内存管理深度剖析

发布时间:2026/8/18 19:16:02
Spark 核心之 Spark 内存管理深度剖析 摘要Spark 内存管理是性能调优的核心战场——理解 UnifiedMemoryManager 的 Execution/Storage 动态借用机制、Tungsten 页式分配、Off-Heap 与 On-Heap 的权衡、以及 acquire → evict → spill 的分配溢出流程是避免 OOM、提升 Shuffle 效率的关键。本文从 JVM Heap 分区架构、Unified vs Static 对比、内存分配失败处理链、Tungsten MemoryAllocator、堆外内存与常见 OOM 调优六个维度配合 2 张原创架构图 源码追踪彻底拆解 Spark 内存管理全貌。关键词UnifiedMemoryManager, Execution Memory, Storage Memory, Tungsten, Off-Heap, Eviction, Spill, OOM 调优一、开篇你的 Executor 内存花在哪了一个spark.executor.memory4g的 Executor实际可用内存远小于 4GB。Spark 将 JVM Heap 划分为三层4GB Executor Heap 默认划分 ├── Reserved Memory: 300MB (固定不可调) ├── User Memory: 1.48GB (1 - 0.6) × (4G - 300M) │ └── 存放 RDD 闭包引用、用户自定义数据结构 └── Spark Memory (统一池): 2.22GB 0.6 × (4G - 300M) ├── Storage Memory: 1.11GB (默认 50%) └── Execution Memory: 1.11GB (默认 50%) 两者可动态互借二、UnifiedMemoryManager 架构全景2.1 三层 Heap 架构// UnifiedMemoryManager.getMaxExecutionMemory()valmaxMemorysystemMemory-reservedMemory// 可用总内存valsparkMemorymaxMemory*conf.get(MEMORY_FRACTION)// Spark 池valstorageMemorysparkMemory*conf.get(STORAGE_FRACTION)// Storage 初始占比valexecutionMemorysparkMemory-storageMemory// Execution 初始占比// 动态借用: Execution 可借用 Storage 空闲部分// Storage 也可借用 Execution 空闲部分但 Execution 需要时可踢回2.2 动态借用规则Execution 借用 Storage: Storage 有闲置 → Execution 直接拿走 → 不归还 Storage 借用 Execution: Execution 有闲置 → Storage 暂时使用 → Execution 需要时会触发 Eviction → 淘汰 Storage Block 核心: Execution 优先级高于 Storage acquireExecutionMemory() → 先用自己的 pool → 不够→借Storage→还不够→spill磁盘三、内存分配与溢出流程3.1 三步处理MemoryConsumer.acquireMemory(requiredSize): ① 检查 Execution Pool 余额 → 够用直接分配 ② 余额不足 → evictBlocksToFreeSpace(required) - 从 Storage Pool 淘汰 RDD Block 缓存 ③ 仍不够 → MemoryConsumer.spill() 自身溢写磁盘 - 释放内存后重试 acquire3.2 核心源码// UnifiedMemoryManager.acquireExecutionMemory()overridedefacquireExecutionMemory(numBytes:Long,...):Long{synchronized{// 尝试从 Storage 借: 淘汰 Storage Block 释放空间valmemoryReclaimableFromStoragemath.max(storagePool.memoryFree,storagePool.poolSize-storageRegionSize)valspaceReclaimedstoragePool.evictBlocksToFreeSpace(...)// 更新可用总量valmaxToGrantmath.min(numBytes,math.max(0,maxExecutionMemory-executionPool.poolSize))executionPool.incrementPoolSize(maxToGrant)maxToGrant}}四、Tungsten 页式内存管理// Tungsten 内存模型MemoryAllocator(Heap/Unsafe)→ allocate(size)→ MemoryBlock(obj/offset,length)TaskMemoryManager → allocatePage(size)→64-bit 逻辑地址 → pageTable 逻辑地址到物理地址映射 → 高13bit:pageNumber(最多8192页)→ 低51bit:offsetInPage 优势:├── 不受 GC 影响Unsafe 模式 ├── 精确控制内存生命周期 ├──64位单一地址空间 └── 页式管理减少碎片spark.memory.offHeap.enabledtrue # 开启堆外内存 spark.memory.offHeap.size2g # 堆外大小 # 堆外内存独立于 JVM Heap不受 GC 影响五、Unified vs Static — 为什么必须 UnifiedStaticMemoryManager (Legacy) 问题: Execution 池 Storage 池 固定分割互不借用 → Execution 满而 Storage 空 → 直接 OOM UnifiedMemoryManager: 统一池 动态借用 → 利用率最大化 Spark 1.6 默认 → 生产环境唯一推荐六、OOM 场景与调优常见 OOM① User Memory OOM: 闭包引用了大对象 → 广播变量解决 ② Execution Memory OOM: Shuffle 数据量过大 → 增大 fraction ③ Storage Memory OOM: 缓存过多 → 减少缓存或用 DISK_ONLY ④ Off-Heap OOM: spark.memory.offHeap.size 不足 → 增大调优公式Execution 可用内存 (最小保证) (Heap - 300MB) × spark.memory.fraction × (1 - spark.memory.storageFraction) 调优策略: ① Shuffle 密集型: 增大 fraction (0.6→0.75) 或 减少 storageFraction (0.5→0.3) ② Cache 密集型: 增大 storageFraction (0.5→0.7) ③ GC 压力大: 开启堆外内存 ④ User Memory 不足: 增大 Heap 或 减少闭包变量大小七、总结三层 HeapReserved(300M) User((1-f)×Heap) Spark(f×(Heap-300M))。Execution/Storage 动态借用。分配失败链Execution Pool → 借 Storage(evict) → 自身 spill 磁盘。Execution 优先级 Storage。调优关键fraction 控 Spark 池大小、storageFraction 控 Storage 占比、offHeap 解 GC 之困。作者starzy博客blog.starzy.cnGitHubstarzy1990.github.io专注 AI Agent · LangGraph · RAG · 大数据架构 · 数据工程实践