spark中的内存管理

概述

本编文章的内容:

  1. 在executor端,内存的管理
  2. 在driver端,内存的管理

1. executor内存管理

1.1 堆内存布局

## UnifiedMemoryManager
private def getMaxMemory(conf: SparkConf): Long = {
    val systemMemory = Runtime.getRuntime.maxMemory
    val reservedMemory = 300 * 1024 * 1024
    val usableMemory = systemMemory - reservedMemory
    val memoryFraction = conf.getDouble("spark.memory.fraction", 0.6)
    (usableMemory * memoryFraction).toLong
  }

如代码所示,堆内存被分为3块,一块是预留内存,一块是额外使用内存,一块是spark用来管理的堆内存,值如下:

  1. systemMemory的值为eden区 + 一个surivior + old区
  2. 预留内存reservedMemory为300M
  3. 默认情况下,额外使用内存为剩余内存的40%
  4. 默认情况下,通过UnifiedMemoryManager管理的内存为剩余内存的60%

里面也包含了一层含义,在整个executor的运行周期中,executor所有组件使用的内存通常不会超过300M,由于用户在rdd的transform或action中使用的内存不可预知,spark.memory.fraction应该由用户自己来控制,如果用户在自己的程序中使用的内存(如没有使用Map、List等数据结构来存储数据)较少,可以相应的调大spark.memory.fraction的值,这对shuffle和cache都有好处;当然这也不是必须的,Reduce spark.memory.fraction default to avoid overrunning old gen in JVM default config描述了一种情况:当spark.memory.fraction设置过大时,比如0.75,当需要缓存大量数据到堆内存中时,会导致长时间的full gc,原因如下:

  1. JVM的参数NewRatio默认是2,说明老年代占用的堆大小的66%
  2. 在使用堆内存缓存数据时,最大的内存占比为spark.memory.fraction,即75%
  3. 此时数据不能完全放入到old区而导致full gc,影响程序性能

1.2 内存管理

Spark通过MemoryManager来管理内存,它有两个实现类:StaticMemoryManagerUnifiedMemoryManager,前者基本没有用了,后者有如下特点:

  1. 内存分为2部分,一部分为存储内存,另一个部分为执行内存
  2. 默认情况下,存储内存占整个管理内存的50%,由spark.memory.storageFraction决定,其余的为执行内存
  3. 存储内存执行内存在对方有空闲空间时,可以互借;但是,执行内存不会主动释放,如果执行内存借了存储内存,此时存储内存不够需要执行内存归还,被借的内存由于执行内存占用不会归还。而当执行内存不够而存储内存借用的执行内存会被归还

UnifiedMemoryManager除了使用上述的堆内存外,还可以使用堆外内存,由spark.memory.offHeap.enabled开启,由spark.memory.offHeap.size确定堆外内存的大小,堆外内存也分为存储内存执行内存存储内存占比也是由spark.memory.storageFraction决定

spark_内存分布概览

1.3 执行内存的使用

spark_shuffle_使用执行内存概览.PNG

如果需要使用MemoryManager管理的执行内存,都需要继承MemoryConsumer,在学习代码的过程中,发现堆内存与堆外内存并没有混合使用,即堆外内存与堆内存不会互借(如果有,请读者不吝指出),可能实现比较麻烦,我总结了上图的内存使用场景。其实上图也比较好理解,因为ExternalSorterExternalAppendOnlyMap里面的数据都是反序列化的,所以需要使用堆内存;而ShuffleExternalSorter的数据都是序列化的,可在堆内也可在堆外,可能因为取值实现起来麻烦所以堆外内存与堆内存不会互借
每个任务能使用的最大执行内存为该executor执行内存 / 该executor正在执行的任务数,最小执行内存为该executor执行内存 / 2 * 该executor正在执行的任务数,当任务需要的内存大于最小执行内存,但是实际能分配给它的内存又小于最小执行内存时,则会阻塞等待,直到其它的任务释放完内存后唤醒它。

1.4 存储内存的使用

spark_shuffle_使用存储内存概览

在spark-core中,只有两种情形会是使用存储内存,一种是RDD需要缓存,二是广播变量,其消耗的内存类别如上图。

1.5 未被MemoryManager管理的内存使用

  1. 各个组件用来缓存的数据的数据结构,如MapList;这是走JVM管理的
  2. 用户在自己的代码中使用的数据结构
  3. NettyBlockTransferService向外传输数据和接收数据,当数据小于spark.storage.memoryMapThreshold(默认2MB),分配传输的ByteBufferHeapByteBuffer,会分配在堆上;但是最终netty会使用堆外内存(默认)将数据传输出去
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容