Spark的内存管理机制

本来是想着直接分析下UnsafeShuffleWriter源码内部是怎么实现的,碰到的一个尴尬的问题是代码内部涉及到了很多关于内存分配相关的代码,而且它使用了Tungsten优化,更加看不懂细节了,所以决定先了解下Spark的内存管理机制然后再看Shuffle的实现。

之前在“SparkContext初始化流程”那篇文章中提到过,内存管理是MemoryManager实现的,在SparkEnv中被初始化。本文不关注Driver上的MemoryManager,只分析Executor上的内存是如何管理的。

 

Spark1.6之前的静态内存管理机制介绍:

因为默认已经不使用这种方式了,所以这里只是提一提,结构如下所示,各个部分的作用稍后会解释(图片皆来源与“参考”中的“1”):

堆内:

堆外:

 

Spark1.6之后的动态内存管理:

堆内:

Storage: 用于broadcast/cache/persist数据的存储

Execution: 用于Shuffle,例如join/agg/sort等操作都是在这部分内存中执行

Other(User Memory): 存储用户的数据结构,个人理解不是对RDD的操作用的都是这部分内存,例如在Executor端创建了一个HashMap

System Reserved: Spark给自身预留的内存,至少300M

 

System Reserved大小由”spark.testing.reservedMemory”参数控制

Storage+ Execution大小由“spark.memory.fraction”控制,默认0.6,个人感觉这个貌似可以调大…但是太大容易造成GC时间偏长,见“参考2”

Storage+ Execution所用内存中Execution所占的比例由“spark.memory.storageFraction”控制,默认0.5,官网建议不要改动这个配置,并且和1.6之前不同的是,它们之间的边界变成了“软边界”,内存可以在一定情况下相互借用。

(ps:Unroll已经完全归Storage管理了,它的大小不再固定)

 

堆外:

可以看到堆外和之前还是一样的,主要改动的是堆内。现在堆内的存储内存和执行内存可以动态调整,相互占用了(但是会优先保证执行内存足够)。动态内存机制如下:

查看UnifiedMemoryManager中acquire***()相关方法:

  1. Execution向Storage借内存

1.1如果要申请的内存已经超过storagePool.memoryFree的话,那么会向Execution接内存,最多借executionPool.memoryFree。

1.2如果还不够,会调用evictBlocksToFreeSpace(),将不用的block移除,block要求不属于当前RDD并且memoryMode相关,就可以evict。

1.3 寻找可以移除的bloc,知道找到足够多的内存为止,如果找到了给它们加上写锁然后调用blockInfoMemory将它们移除。否则不移除并返回0L

 

      2.Storage向Execution借内存

首先需要明确一点,单个Task不能拥有太多的内存,它被限制在了(1/N -1/2N) * MaxExecutionMemory之间,之所以加了个Max是当前Execution最大的内存,可能像Storage借了一部分,也可能被Storage拿走了一部分。

如果Execution空闲的空间不够时,首先会占用storagePool的空闲内存,如果不够就会要求storagePool归还它占用的那部分内存,还不够就叫storagePool evict掉一些block。

注意:如果一开始进来storagePoolSize 就已经小于 storageRegionSize时,就不能要求evict了,storageRegionSize表示storagePoolSize最开始的大小,表示一开始storagePool的内存就已经不够用了,而且已经被Execution借走了一部分)

 

下面来分析下UnifiedMemoryManager其他的内部实现

初始化:

UnifiedMemoryManager继承自MemoryManager,所以它先初始化父类

//1.初始化内存池

//2.判断tungsten是否启用了堆外内存

//3.判断一个page的大小,限定在1M~64M之间,我这里是16M,和服务器总核数以及内存总大小有关。page是tungsten中引入的优化概念,后面用到它的时候会讲到。

//4.根据模式确定任务在哪里分配内存:(这里是按照上面所说的Page分配内存,Unsafe/Tungsten模式下才会使用到,一般情况下都是直接申请多少个字节)

 

然后就初始化完毕了,可以看到其实一直都是在初始化父类中的属性。

 

这个MemoryManager是对于一个Executor而言的,同一个Executor内部的所有Task都调用它的接口来申请或者释放内存。申请时要指定申请的MemoryMode,表示申请的是堆内内存还是堆外内存

但是只有在使用Tungsten是才会使用到MemoryAllocator和Page的概念,看到目前为止是只有UnSafedShuffleWriter才会用到,可能有其他地方我没看到。

 

单个任务内存管理-TaskMemoryManager:

TaskMemoryManager负责管来一个Task的内存申请和释放,申请和释放内存的功能又依赖于上面的MemoryManager。

一个Task里面有很多地方需要使用到内存,它们都依赖TaskMemoryManager申请内存,被称为MemoryConcumer。申请和释放内存又分了使用Tungsten和非Tungsten模式。暂时先不看它申请和释放内存的方法,等到后面分析Shuffle或者其他API时自然会看到,本人大概就是瞄了一眼,重点了解下Tungsten模式。

 

Tungsten介绍

Tungsten是Spark为了提升JVM带来的性能限制而提出的一个优化计划,主要是考虑如下两个因素:

  • Java对象所占的内存空间大,不像C/C++等更加底层的语言
  • JVM的GC机制在回收大空间时开销大

 

Tungsten优化体现在三个方面:

  1. 内存管理与二进制处理(Memory Management and Binary Processing):不处理Java对象的数据,而是直接处理二进制的数据,并引入了类似OS中的Page,提升性能。(堆外内存)
  2. 缓存敏感计算(Cache-aware computation): 设计算法和数据结构以充分利用memory hierarchy
  3. 动态代码生成(Code generation): 使用code generation去充分利用最新的编译器和CPU的性能

本文主要介绍第一点,后面那两点我也还没细看。

 

Page结构如下:

Tungsten模式下,内存会按照page的方式进行管理。Page上面介绍过,一个Page在UnifiedMemoryManager中默认被限制在1M~64M之间,当然也可以通过配置进行修改。在代码中,Page被封装成MemoryBlock结构,用来表示一段连续的内存。

Page的逻辑地址是一个64位long类型的数,对于堆外的数据,直接给用64为地址可以表示页的位置。但是对于堆内内存来说,由于GC导致引用的地址其实是会变化的,所以64位中的前13bits代表页编号,后51位代表该页在内部的偏移量。映射的过程就是根据页编号去内部查找它的偏移量。

但是一个page的大小并不是2^51,看如下解释,一个page在理论上最大是16G:

(ps后面分析到Shuffle的时候会发现,其实一个Page达不到16G,因为Page存放的数据记录只有27位是用来表示Page内的偏移量的,所以Page最大的大小为2^27Bits=128M)

一个Task所拥有的Page都放在 MemoryBlock[] pageTable数组中,table的大小最大为 1 << 13,即8192。所以一个Task最多可以管理8192*128M=1TB的内存。

按照默认的1M~64M来算的话,一个Task可以管理的内存大小为8G~512G之间。(别忘了一个Executor上可以根据核数并行的运行多个Task,所以也很大了)

 

 

参考:

https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-apache-spark-memory-management/index.html(内存管理机制图示)

https://issues.apache.org/jira/browse/SPARK-15796(Storage+Execution所占比例0.75调整0.6的原因)

https://www.jianshu.com/p/047da3b7550e(内存如何相互借用)

http://www.imooc.com/article/267400(mayBeGrowExecutionPool()分析)

https://www.jianshu.com/p/d328c96aebfd(Tungsten Page结构)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值