文章目录
- 1.堆内和堆外内存规划
- 1.1 堆内内存
- 1.1.1 内存申请与释放
- 1.2 堆外内存
- 2.内存空间分配
- 2.1 静态内存管理(Spark1.6版本以前)
- 2.2 统一内存管理(Spark1.6以后)
- 3.存储内存(Storage)管理
- 3.1 RDD的持久化机制
- 3.2 RDD的缓存过程
- 3.3 淘汰与落盘
主要需要学习的Executor上面的内存管理
1.堆内和堆外内存规划
作为一个JVM进程,Executor的内存管理建立在JVM的内存管理之上,Spark对JVM的堆内内存(On-heap)空间进行更详细的分配,以充分利用内存空间。同时引入堆外内部,使Spark可以直接申请工作节点中的系统内存进行使用。
1.1 堆内内存
堆内内存的大小取决于:
- 启动传入的参数:
-executor-memory
- 或者Spark程序中设置的参数:
spark.executor.memory
【注意】堆内内存共享JVM堆内内存,由JVM进行管理
堆内内存大致被划分为三部分:
- 存储内存:缓存RDD数据和广播数据
- 执行内存:执行
Shuffle
时占用的内存 - 剩余内存:不做规划,有Spark中创建的实例等其他占用
1.1.1 内存申请与释放
- 申请
- Spark中new一个对象实例
- JVM从堆内内存分配空间,创建对象实例后返回引用
- Spark保存该对象的引用,记录该对象占用的内存大小(可能估算,详细看下面序列化和反序列化)
- 释放
- Spark记录该对象释放的内存,删除对象的引用
- 等待JVM的垃圾回收机制对该实例对象占用的空间进行回收
这里需要另一个题外话,JVM对象的序列化和反序列化
背景:
JVM的对象可以进行序列化(将对象转换为二进制字节流,可以简单理解为由非连续的链式空间转换为连续空间),而需要从字节流转换回对象则需要进行反序列化。==>序列化可以节省存储空间,但是增加了序列化和反序列化的计算开销。
导致问题:
- 对于序列化的对象,其占用内存大小可以准确的指导;对于非序列化的对象,其占用的内存是通过周期性采样近似估算得到,不是每新增一个对象都计算一次占用内存大小,这样减少了时间的开销,但可能导致对象实际占用的内存大小远超过记录;
- 另外当某个对象被Spark标记清除,但是JVM还没有将其空间进行回收,导致实际可用内存远小于Spark中记录的可用内存
基于以上两种情况,Spark记录的可用堆内内存并不准确,所以会无可避免的出现OOM
异常。
1.2 堆外内存
为了进一步优化内存的使用以及提高Shuffle
时排序的效率,Spark中引入了堆外(Off-heap)内存,使Spark可以直接工作节点的系统内存中开辟空间,存储经过序列化的二进制数据。
堆外内存可以被精准的申请和释放(Spark直接向操作系统申请,并有操作系统进行释放)
[注]:默认情况下堆外内存并不启用
2.内存空间分配
2.1 静态内存管理(Spark1.6版本以前)
存储内存、执行内存和其他内存的大小在Spark应用程序运行期间均为固定的。
- Storage:保留缓存的数据(如broadcast、cache、persist)
- Execution:保留Shuffle过程的缓存数据
- Other: 存储用户定义数据结构等数据(那些没有缓存的数据)
[弊端]:很难确定某个空间到底会占用多大,容易出现"某个区域空闲空间很大,但还是发生OOM异常"
2.2 统一内存管理(Spark1.6以后)
- 设定了基本的存储内存(Storage)和执行内存(Execution)区域,确定双方拥有的空间范围
- 双方空间都不足时存储到磁盘中,但一方空间不足时可存储到对方空间中(注:存储空间不足表示不足以放下一个完整的Block)
- 执行内存(Execution)的空间被占用后,可以让对方把占用部分转存到磁盘,然后归还空间
- 存储内存(Storage)的空间被占用后,由于涉及Shuffle过程,无法让对方归还
3.存储内存(Storage)管理
3.1 RDD的持久化机制
RDD(其中的数据为多个Partition
)中的数据(原本是在Other
中)经过缓存(cache、persist),这些数据被缓存到storage
模块中,并且是将离散的存储转换为连续的存储,这个过程也称为Unroll
Storage模块在逻辑上以Block为基本存储单位,RDD的每个Partition
经过处理后唯一对应一个Block
;
这里记录一下数据进行缓存的几个级别:
级别 | 解释 |
MEMORY_ONLY | 只缓存到内存中 |
MEMORY_AND_DISK | 先持久化到内存中,不够用时再缓存到磁盘中 |
MEMORY_ONLY_SER | 序列化之后缓存到内存中 |
MEMORY_AND_DISK_SER | 同MEMORY_AND_DISK,但持久化的序列化后的数据 |
DISK_ONLY | 只缓存到磁盘中 |
MEMORY_ONLY_2/MEMORY_AND_DISK_2 | 对持久化数据保留多一个复制 |
3.2 RDD的缓存过程
RDD在缓存到内存之前,Partition
中的数据一般以迭代器的数据结构进行访问。通过Iterator
可以获取分区中每一条序列化或者未序列化的数据项(Record
),这些Record
实际占用JVM堆内内存的other空间,同一个Partition
的不同Record
的存储空间不是连续的。
RDD缓存到内存之后,Partition
被转换成Block
。将Partition
由不连续的存储空间转换为连续存储空间,此过程称为Unroll
- 在静态内存管理中划分了一块固定的区域用于
Unroll
,而在统一内存管理中则没有,但使用动态内存占用规则进行分配空间
3.3 淘汰与落盘
由于同一个Executor的所有计算任务共享有限的存储内存空间,当有新的Block
需要缓存但是剩余空间不足且无法动态占用时,就要对LinkedHashMap
中的就Block
进行淘汰(Eviction
),而被淘汰的Block
如果其存储级别中同时包含存储到磁盘的要求,则需要进行落盘(Drop),否则直接删除该Block
。