spark 基础四:内存分配
Spark作为一个基于内存的分布式计算引擎,其内存管理模块在整个系统中扮演着非常重要的角色。理解Spark内存管理的基本原理,有助于更好地开发Spark应用程序和进行性能调优。

我们都知道,在执行Spark的应用程序时,Spark集群会启动Driver和Executor两种JVM进程,作用分别如下,
Driver:创建SparkContent;划分Job Stage以及转化Task;Executor进程间协调任务的调度;
Executor:执行具体的计算任务,将结果返回给Driver;持久化RDD

Driver的内存管理相对来说较为简单,Spark不做具体规划。
作为一个JVM进程,Executor的内存管理建立在JVM的内存管理之上,Spark对JVM的堆内(On-heap)空间进行了更为详细的分配,以充分利用内存。同时,Spark引入了堆外(Off-heap)内存,使之可以直接在工作节点的系统内存中开辟空间,进一步优化了内存的使用。
- 堆内内存:受JVM管理
- 堆外内存:不受jvm管理

1、堆内内存 on-heap Memory
堆内内存的大小,由Spark应用程序启动时的–executor-memory或spark.executor.memory参数配置。
Executor内运行的并发任务共享JVM堆内内存。
Executor内存空间的分配:
- 存储Storage内存区域:缓存RDD和广播(Broadcast)数据;
- 执行Execution内存区域:执行Shuffle时占用的内存;
- 其他:Spark内部元数据,或者用户定义的数据结构

Spark为存储内存和执行内存的管理提供了统一的接口——MemoryManager,同一个 Executor内的任务都调用这个接口的方法来申请或释放内存。
MemoryManager有两种具体实现,Spark1.6 之后默认为统一管理(UnifiedMemoryManager)方式,1.6 之前采用的静态管理(StaticMemoryManager)方式仍被保留,可通过配置spark.memory.useLegacyMode参数启用。
两种方式的区别在于对空间分配的方式。
1.1 StaticMemoryManager 静态管理
存储内存、执行内存和其他内存的大小在Spark应用程序运行期间均为固定的,但用户可以应用程序启动前进行配置;
存储内存默认占用堆内内存的60%;
执行内存默认占用堆内内存的20%;——shuffle调优时,可以调整这个比例,从而给shuffle过程更多的内存
其他内存默认占用堆内内存的20%;
这种方式有个缺点,那就是如果用户不熟悉Spark的存储机制,或没有根据具体的数据规模和计算任务或做相应的配置,容易出现存储内存和执行内存中的一方剩余大量的空间,而另一方却早早被占满,不得不淘汰或移出旧的内容以存储新的内容,造成程序执行缓慢甚至失败
1.2 UnifiedMemoryManager 统一管理
Spark1.6 之后引入的统一内存管理机制,与静态内存管理的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域。
存储内存+执行内存=统一内存 默认占用堆内内存的60%;
存储内存和执行内存 默认各占用统一内存50%;——可用spark.storage.storageFraction参数调整
其他内存 默认占用堆内内存的40%;

优点:在一定程度上提高了堆内和堆外内存资源的利用率,降低了开发者维护Spark内存的难度
动态占用机制的规则如下:
- 1.设定基本的存储内存和执行内存区域(spark.storage.storageFraction参数),该设定确定了双方各自拥有的空间的范围
- 2.双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的Block)
- 3.执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后“归还”借用的空间
- 4.存储内存的空间被对方占用后,无法让对方“归还”,因为需要考虑Shuffle过程中的很多因素,实现起来较为复杂
2、堆外内存
在默认情况下,堆外内存并不启用,可通过配置spark.memory.offHeap.enabled参数启用,并由spark.memory.offHeap.size参数设定堆外空间的大小。
堆外内存主要存储经过序列化的二进制数据。
Spark可以直接操作系统堆外内存,减少了不必要的内存开销,以及频繁的GC扫描和回收,提升了处理性能。堆外内存可以被精确地申请和释放,而且序列化的数据占用的空间可以被精确计算,所以相比堆内内存来说降低了管理的难度,也降低了误差。
堆外的空间分配较为简单,除了没有 other空间,存储内存、执行内存的大小同样是固定的,所有运行中的并发任务共享存储内存和执行内存。

2.1 StaticMemoryManager 静态管理
堆外的空间分配较为简单,存储内存和执行内存的大小同样是固定的,可用的执行内存和存储内存占用的空间大小直接由参数spark.memory.storageFraction决定,由于堆外内存占用的空间可以被精确计算,所以无需再设定保险区域。

2.2 UnifiedMemoryManager 统一管理

3、总结
凭借统一内存管理机制,Spark在一定程度上提高了堆内和堆外内存资源的利用率,降低了开发者维护Spark内存的难度,但并不意味着开发者可以高枕无忧。譬如,所以如果存储内存的空间太大或者说缓存的数据过多,反而会导致频繁的全量垃圾回收,降低任务执行时的性能,因为缓存的RDD数据通常都是长期驻留内存的[5]。所以要想充分发挥Spark的性能,需要开发者进一步了解存储内存和执行内存各自的管理方式和实现原理。
如果是你的计算比较复杂的情况,使用新型的统一内存管理 (Unified Memory Management) 会取得更好的效率,但是如果说计算的业务逻辑需要更大的缓存空间,此时使用老版本的固定内存管理 (StaticMemoryManagement) 效果会更好。
参考:https://blog.csdn.net/dongdouzin/article/details/79753155