Spark 性能优化 (二):内存模型

发布于:2025-02-11 ⋅ 阅读:(10) ⋅ 点赞:(0)

在大数据计算和Java(包括Spark)中,**堆内存(On-Heap Memory)堆外内存(Off-Heap Memory)**是两个重要的概念,主要涉及内存管理、GC(垃圾回收)开销以及性能优化。下面从原理、区别、使用场景等方面进行详细解读。

1. 堆内存(On-Heap Memory)

定义

堆内存指的是 JVM(Java Virtual Machine)管理的内存空间,它由Java进程启动时分配,并受JVM的垃圾回收(GC)机制管理。在Spark、Flink等大数据计算框架中,默认情况下数据对象会存储在堆内存中。

特点

  • 由JVM管理,受GC控制。
  • 受JVM的-Xms(最小堆大小)和-Xmx(最大堆大小)参数限制。例如:
    java -Xms4G -Xmx8G -jar app.jar
    
    这里设置最小4GB,最大8GB的堆内存。
  • GC可能会影响性能:当堆内存数据过多时,GC操作会变得频繁,从而影响程序运行效率。
  • 内存访问速度相对较快。

使用场景:适用于存放生命周期较短的对象,例如

  • Java中的对象(List、Map等)
  • Spark RDD的缓存(默认使用堆内存)
  • 运行时计算所需的临时对象

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

定义

堆外内存是不受JVM管理的内存,即直接向操作系统申请的内存,通常是通过sun.misc.UnsafeByteBuffer.allocateDirect()进行分配。

特点

  • JVM无法直接管理,不会受GC影响,因此可以减少GC带来的延迟,提高性能。
  • 需要手动释放(否则可能导致内存泄漏),通常使用sun.misc.UnsafeDirectByteBuffer进行管理。
  • 访问速度可能略低于堆内存,但由于减少了GC影响,在大数据场景下通常更高效。
  • 可以突破JVM的堆大小限制(如-Xmx设置的上限)。

使用场景:大数据计算(如Spark、Flink)

  • Spark的Tungsten优化(基于Unsafe的序列化)会使用堆外内存来存储数据,减少GC压力,提高计算效率。
  • Spark 2.x开始支持堆外存储,可以通过以下参数控制:
spark.conf.set("spark.memory.offHeap.enabled", true) 
spark.conf.set("spark.memory.offHeap.size", "2g") // 设置2GB的堆外内存

3. 堆内存 vs 堆外内存

对比项 堆内存(On-Heap Memory) 堆外内存(Off-Heap Memory)
管理方式 JVM管理,由GC回收 由操作系统管理,需手动释放
访问速度 访问速度快 略慢(但减少了GC的影响)
GC影响 受GC影响,可能会导致长时间停顿 不受GC影响,适用于高性能计算
适用场景 适用于小规模数据、短生命周期对象 适用于大规模数据处理,如Spark/Flink
内存上限 -Xmx限制 可以突破JVM的限制

4. Spark 内存区域划分

每个Executor的JVM堆内存(-Xmx)主要可以分为以下几个部分:

内存区域 说明
Reserved Memory 预留内存,防止OOM(默认300MB)
User Memory 用户内存(执行任务中的变量、数据结构等)
Spark Unified Memory Spark的统一内存管理(存储+计算)
Storage Memory 用于缓存RDD、DataFrame等数据
Execution Memory 用于Shuffle、Join、Sort等计算
  1. Reserved Memory(预留内存)

    • Spark默认会预留 300MB 内存,它被用来存储各种 Spark 内部对象,例如存储系统中的 BlockManager、DiskBlockManager 等等。
    • 这个值可以通过 spark.testing.memory 进行调整(不建议修改)。
  2. User Memory(用户内存)

    • 存放用户代码中的数据结构、对象等。
    • 例如 collect()toLocalIterator() 可能会消耗大量此类内存。
    • 占用总Executor内存的约 20-30%,但未做严格限制。
  3. Unified Memory(统一内存)

    • Spark 2.x 之后引入,Storage MemoryExecution Memory 共享内存,提高利用率。
    • Spark会动态调整 Storage MemoryExecution Memory 之间的比例,优先满足计算需求。
  4. Storage Memory(存储内存)

    • 用于缓存 RDD、DataFrame、广播变量等数据。
    • 当 Execution Memory 不够时,可能会被回收(动态分配)。
    • spark.memory.storageFraction 控制默认比例(默认 0.5)。
  5. Execution Memory(计算内存)

    • 用于Shuffle、Sort、Aggregation等操作的临时数据存储。
    • spark.memory.fraction 控制分配给 Storage + Execution 的内存比例(默认 0.6)。
    • 如果 Execution 需要更多空间,Storage 可能会被回收。

4.1 Storage Memory 和 Execution Memory 抢占规则

  • 如果对方的内存空间有空闲,双方就都可以抢占;
  • 对于 RDD 缓存任务抢占的执行内存,当执行任务有内存需要时,RDD 缓存任务必须立即归还抢占的内存,涉及的 RDD 缓存数据要么落盘、要么清除;
  • 对于分布式计算任务抢占的 Storage Memory 内存空间,即便 RDD 缓存任务有收回内存的需要,也要等到任务执行完毕才能释放。

5. 内存控制推荐策略

Spark的存储和计算内存默认是堆内存,但可以启用堆外内存优化:

spark.conf.set("spark.memory.offHeap.enabled", true)  // 启用堆外内存
spark.conf.set("spark.memory.offHeap.size", "4g")    // 设定4GB堆外内存
  • 如果数据量较小(<10GB),建议使用堆内存(默认)。
  • 如果数据量较大,建议开启堆外内存,减少GC影响,提高计算效率。
数据规模 推荐存储方式
< 10GB 堆内存
10GB - 100GB 适量使用堆外内存
> 100GB 主要使用堆外内存

合理配置堆内存和堆外内存可以避免OOM(OutOfMemoryError)并提高计算性能