Spark统一内存划分

1. Executor内存逻辑架构

在这里插入图片描述

  1. 堆内存,由JVM分配和回收,由spark.executor.memory控制大小,JVM中序列化的对象是以字节流形式,其占用内存大小可直接计算,对于非序列化对象,其占用的内存是通过周期性地采样近似估算,且被spark标记为释放的对象实例也有可能并没有被JVM回收,所以spark并不能准确记录实际可用堆内存,也就无法避免内存溢出
  2. 非堆内存,不受JVM管理,有两部分,其中一部分通常是yarn模式中通过spark.executor.memoryOverhead配置,该部分内存用于虚拟机自身的开销(字符串、NIO和其它一些本地开销);另一部分通过spark.memory.offHeap.enable/size结合配置,该部分由spark直接使用于存储内存和任务内存,从2.0开始不再依赖第三方内存系统Tachyon,而是基于JDK自带的Unsafe API实现堆外内存管理,堆外内存可以精确地申请和释放,减少了不必要的额外开销。
  3. 系统内存(systemMemory):这里指的是JVM可用的最大内存,可通过Runtime.getRuntime.maxMemory获得该值,系统内存并不等于分配的堆内存,由于年轻代GC采用复制算法,所以有一块survivor内存区需要保留,即systemMemory=堆内存-survivor
  4. 可用内存(usableMemory):这部分内存是用户代码能直接影响到的,可用内存=系统内存 - Reserved,其中Reverved为固定300M的保留内存,用于spark系统内部使用。
  5. 应用内存:主要用于存储用户代码生成的数据对象,这些数据对象被缓存之前就是处于应用内存空间
  6. 存储内存与执行内存:存储内存用于缓存数据,执行内存主要用于满足 Shuffle、 Join、 Sort、 Aggregation 等计算过程中对内存的需求,通过spark.memory.storageFraction控制两者比例,默认平分,两部分内存之间还可以进行动态占用:
    • 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后归还借用空间
    • 存储内存空间被对应占用后,无法让对方归还,因为shuffle过程中的很多因素无法实现

2. Executor 界面内存计算

在spark任务监控界面的Executors菜单中有一列“Storage Memory”显示当前“已用/总可用存储内存”情况,当使用静态内存机制时,此列总大小确实为存储内存,但使用统一内存机制时:

显示总内存 = 存储内存+执行内存

虽然两部分内存可以动态占用,但应该总不会把执行内存全部借光。此处分析统一内存该值是怎么个计算方式,既然知道显示的总内存是指存储内存和执行内存总和,那么结合逻辑架构图可以直观地看到组成方式:

显示总内存 =(堆内存储内存+堆内执行内存)+(堆外存储内存+堆外执行内存) 
           =(可用内存-应用内存)+ offHeap
		   = 可用内存 * spark.memory.fraction + offHeap
           =(系统内存-300M)* 0.6 + offHeap
           =(spark.executor.memory – survivor – 300M)* 0.6 + offHeap
           =(Runtime.getRuntime.maxMemory – 300M)* 0.6 + offHeap

这公式中survivor内存大小是核心,如果确定了就能确定总内存大小。按理解,可以通过NewRatio和SurvivorRatio计算出survivor大小的,但是通常指定了最大堆内存,但是jvm并不会初始化时就会申请到最大内存,则是动态增加的,所以survivor大小也只是一个估计值(约为90%)。可以通过指定Xms等于最大堆内存或者禁用UseAdaptiveSizePolicy,这样survivor就可以按比例计算出来。
另外需要注意一点,页面是通过请求”allexecutors”接口返回的数据,总内存对应的返回字段是maxMemory,单位是字节,而展现为GB时直接除1000而非1024,且最终结果小数位是五舍四入。

3. UnrollMemory理解

spark的rdd在缓存到存储内存之前,每条数据的对象实例都处于JVM进堆内内存的应用内存,即便同一个分区内的数据在内存空间也不是连续的(更可能不在同一物理节点?),具体分布由JVM管理,上层通过scala中的迭代器来访问。当rdd持久化储存内存之后,partition对应转换为block,此时数据在存储内存空间(堆内或堆外)中将连续的存储,这里将分区由不连续的存储空间转换为连续的存储空间的过程,就是unroll操作

在静态内存机制中,内存的构成部分有一个叫unroll内存,该部分从存储内存中独立划分 约占20%。但是在统一内存机制中,unroll内存却不存在了,其实在spark抽象的内存管理器(MemoryManger)中抽象了三个方法:

acquireStorageMemory
acquireUnrollMemory
acquireExecutionMemory

无论是统一内存(UnifiedMemoryManager)还是静态内存(StaticMemoryManager)按自己的逻辑实现了3个方法,统一内存中已经把存储内存和unroll内存合并,代码实现层面上UnifiedMemoryManager.acquireUnrollMemory其实也只是简单地调用了一下acquireStorageMemory

4. 参考

《spark sql内核剖析》
https://zhuanlan.zhihu.com/p/115888408
https://www.jianshu.com/p/87a36488993a
https://blog.youkuaiyun.com/lemonZhaoTao/article/details/81990408

### Spark内存管理机制概述 Spark内存管理机制主要围绕其运行环境中的 Executor 来设计,基于 JVM 的内存模型进行扩展和优化。Spark内存划分为多个功能区,主要包括 **存储内存(Storage Memory)** 和 **执行内存(Execution Memory)**[^1]。通过这种划分Spark 实现了更高效的资源利用,并减少了因内存不足而引发的异常。 #### 存储内存与执行内存的动态分配 在 Spark 中,存储内存用于缓存 RDD 数据集和其他持久化数据结构;执行内存则负责任务运行期间所需的临时空间,例如 Shuffle 操作产生的中间数据。为了提高灵活性,Spark 提供了一种动态共享机制,允许这两类内存之间相互借用。如果某一部分内存不足,则可以从另一部分借取多余的空间,前提是后者有足够的剩余容量[^4]。 具体而言: - 当 Storage 内存不足以容纳新加入的数据时,它可能会尝试从 Execution 内存中获取额外空间。 - 如果两者都面临压力,则超出的部分会被写入磁盘以缓解内存紧张状况。 这一策略显著提升了系统的鲁棒性和效率,同时也降低了手动配置复杂度。 #### 统一内存管理(Unified Memory Management) 自 Spark 1.6 开始引入统一内存管理系统以来,进一步简化了内存分区逻辑。在此模式下,整个可用堆内内存被统一看作单一池子来进行分配决策,而不是严格区分不同用途的具体边界线[^5]。这种方式不仅便于理解操作也更加贴近实际需求场景下的表现形式: - 默认情况下,总可用内存计算公式如下所示: `可用内存 = (systemMaxMemory - ReservedMemory) * spark.memoryFraction` - 其中,“`ReservedMemory`”代表保留给非 Spark 使用的基础开销区域大小,默认约为300MB; - 参数“`spark.memoryFraction`”用来定义可供 Spark 自由支配的比例,默认值设为0.6表示六成份额留给框架本身运作所需。 随后再依据特定权重因子细分出专门服务于 storage 或 execution 功能区块的实际尺寸: - 可用存储内存:`(systemMaxMemory - ReservedMemory) * spark.memoryFraction * spark.storage.storageFraction` - 可用执行内存:`(systemMaxMemory - ReservedMemory) * spark.memoryFraction * (1-spark.storage.storageFraction)` 以上述方式构建起来的整体架构既兼顾到了传统意义上固定配额制的优点——清晰明了易于掌控全局态势变化趋势的同时又能灵活应对突发性的局部波动挑战. ### 垃圾收集(Garbage Collection, GC)调优建议 由于 Spark 是运行于 Java 虚拟机(JVM)之上的分布式计算平台,所以不可避免地涉及到大量的短生命周期对象创建销毁过程。这使得合理选择合适的垃圾回收算法变得尤为重要[^2]: - 对于大多数生产环境中推荐采用 G1 收集器因为它能够在提供较好吞吐量的前提下保持较稳定的延迟特性; - CMS 并发标记清除收集器也是一个不错的选择特别是在注重低延时时段的应用场合. 另外还可以借助一些 JVM 启动参数来微调行为特征: - `-Xmx`: 设置最大堆大小. - `-XX:MaxGCPauseMillis`: 控制期望达到的最大GC停顿时间目标毫秒数. - 配置项如 `spark.executor.extraJavaOptions`, 它们能够传递必要的指令至各个 worker nodes 上启动 executors 所使用的 jvm 实例当中去影响最终效果呈现出来的方式方法等等[^2]. 最后值得注意的是随着版本迭代更新不断推进某些旧版中存在的问题可能已经得到修复或者替代方案出台因此务必关注官方文档说明确保所做的一切努力都是建立最新稳定可靠基础上面展开来的最佳实践路径之上[^3]. ```bash export SPARK_JAVA_OPTS="$SPARK_JAVA_OPTS \ -Dspark.executor.memory=8g \ -Xms8g \ -Xmx8g \ -XX:+UseG1GC" ``` 上述脚本片段展示了如何设置 executor 的初始 heap size (`-Xms`) 和最大 heap size (`-Xmx`) ,同时启用了 G1 Garbage Collector(`-XX:+UseG1GC`). 这些设置有助于改善大型集群环境下长时间运行 job 的稳定性及性能表现. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值