一、简介
1、背景
Spark是基于内存的分布式计算引擎,内存模型与管理是核心知识点,理解它能更好地开发Spark应用和进行性能调优(解决作业GC耗时长问题—主要是Young GC)。
2、总体架构 & 运行流程
Spark整体运行流程:
- 构建运行环境。由Driver创建SparkContext,进行资源申请、任务分配与监控;
- 分配资源。SparkContext和Cluster Manager通信,为Executor申请资源,进行任务分配和监控,启动进程;
- 分解Stage,申请Task。 SparkContext构建DAG图,然后分解为Stage,每个Stage的TaskSet发给TaskScheduler。Executor向Driver申请Task.
- 运行 & 注销。Task在Executor上运行,执行完反馈给TaskScheduler与DAGScheduler,然后释放资源。
从整体流程看,Driver端创建SparkContext、提交作业、协调任务;Executor端执行Task。从内存使用角度看,Executor端内存设计比较复杂,下面予以概述。
二、Executor端内存设计
2.1 堆划分
Worker节点启动的Executor是一个JVM进程,因此Executor内存管理是建立在JVM内存管理上(On-Heap堆内内存),同时Spark也引入了Off-Heap(堆外内存),使之可以直接在系统内存中开辟空间,避免了在数据处理过程中不必要的序列化和反序列化开销,同时降低了GC开销。
2.1.1、On-Heap(堆内)
堆内内存大小由Spark应用程序启动时的-executor-memory或Spark.executor.memory参数配置。Executor内运行的并发任务共享JVM堆内内存,这些任务在缓存RDD或广播数据时被划为存储(Storage)内存,而这些任务在执行Shuffle时,占用的内存被划为执行(Execution)内存,剩余部分不做特殊规划,那些Spark内部对象实例或用户自定义Spark应用程序中的对象实例,均占用剩余空间。不同管理模式下,这三部分占用的空间大小各不相同。
Spark对堆内内存的管理只是逻辑上“规划式”的管理,对象实例占用内存的申请和释放都是由JVM完成,而Spark只是在申请后和释放前记录这些内存,下面看看具体流程:
【1】 申请内存
1、Spark在代码中new一个对象实例;
2、JVM从堆内内存中分配空间,创建对象并返回对象引用;
3、Spark会保存该对象引用,记录对象占用的内存。
【2】释放内存
1、Spark记录该对象释放的内存,删除该对象的引用
2、等待JVM垃圾回收机制来释放掉对象占用的堆内内存。
Spark序列化小知识:
JVM对象可以以序列化方式存储,序列化的过程是将对象转换为二进制字节流,本质上可以理解为将非连续空间的链式存储转化为连续空间或块存储,在访问时需要进行序列化的逆过程—反序列化,将字节流转化为对象,序列化的方式可以节省存储空间,但增加了存储和读取时的计算开销。
Spark中序列化的对象会以字节流形式存在,占用内存大小可以直接计算,而对于非序列化的对象,占用的内存只能通过周期性地采样近似估算得到,即并不是每次新增数据项都会计算一次占用内存大小,这种方法降低时间开销,但可能误差较大,导致某一时刻实际内存