在大数据计算和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
(最大堆大小)参数限制。例如:
这里设置最小4GB,最大8GB的堆内存。java -Xms4G -Xmx8G -jar app.jar
- GC可能会影响性能:当堆内存数据过多时,GC操作会变得频繁,从而影响程序运行效率。
- 内存访问速度相对较快。
使用场景:适用于存放生命周期较短的对象,例如
- Java中的对象(List、Map等)
- Spark RDD的缓存(默认使用堆内存)
- 运行时计算所需的临时对象
2. 堆外内存(Off-Heap Memory)
定义
堆外内存是不受JVM管理的内存,即直接向操作系统申请的内存,通常是通过sun.misc.Unsafe
或ByteBuffer.allocateDirect()
进行分配。
特点
- JVM无法直接管理,不会受GC影响,因此可以减少GC带来的延迟,提高性能。
- 需要手动释放(否则可能导致内存泄漏),通常使用
sun.misc.Unsafe
或DirectByteBuffer
进行管理。 - 访问速度可能略低于堆内存,但由于减少了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等计算 |
Reserved Memory(预留内存)
- Spark默认会预留 300MB 内存,它被用来存储各种 Spark 内部对象,例如存储系统中的 BlockManager、DiskBlockManager 等等。
- 这个值可以通过
spark.testing.memory
进行调整(不建议修改)。
User Memory(用户内存)
- 存放用户代码中的数据结构、对象等。
- 例如
collect()
、toLocalIterator()
可能会消耗大量此类内存。 - 占用总Executor内存的约 20-30%,但未做严格限制。
Unified Memory(统一内存)
- Spark 2.x 之后引入,Storage Memory 和 Execution Memory 共享内存,提高利用率。
- Spark会动态调整
Storage Memory
和Execution Memory
之间的比例,优先满足计算需求。
Storage Memory(存储内存)
- 用于缓存 RDD、DataFrame、广播变量等数据。
- 当 Execution Memory 不够时,可能会被回收(动态分配)。
spark.memory.storageFraction
控制默认比例(默认0.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)并提高计算性能