spark的内存分配管理

SPARK的内存管理器

StaticMemoryManager,UnifiedMemoryManager

1.6以后默认是UnifiedMemoryManager.

这个内存管理器在sparkContext中通过SparnEnv.create函数来创建SparkEnv的实例时,会生成.

通过spark.memory.useLegacyMode配置,可以控制选择的内存管理器实例.

如果设置为true时,选择的实例为StaticMemoryManager实例,否则选择UnifiedMemoryManager实例.默认情况下这个值为false.

val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode"false)
val memoryManager: MemoryManager =
  if (useLegacyMemoryManager) {
    new StaticMemoryManager(confnumUsableCores)
  } else {
    UnifiedMemoryManager(confnumUsableCores)
  }

 

UnifiedMemoryManager

这个实例生成时,最大内存的得到方法:

1,根据当前JVM的启动内存,减去300MB,

   这个300MB可以通过spark.testing.reservedMemory配置得到.

2,最大内存值,通过1计算出来的内存值,与spark.memory.fraction配置的系数进行相乘.默认是0.75.

示例:如果JVM配置的内存为1GB,那么可使用的最大内存为(1GB-300MB)*0.75

需要的配置项:

配置项spark.memory.fraction,默认值0.75,这个配置用于配置当前的内存管理器的最大内存使用比例.

配置项spark.memory.storageFraction,默认值0.5,这个配置用于配置rdd的storage与cache的默认分配的内存池大小.

配置项spark.memory.offHeap.size,默认值0,这个配置用于配置非堆内存的大小,默认不启用.这个不做分析.

 

在实例生成后,默认会根据storage的内存权重,总内存减去storage的内存权重,生成两个内存池storageMemoryPoolonHeapExecutionMemoryPool.

 

onHeapExecutionMemoryPool用于在执行executor的shuffle操作时,使用的内存,

storageMemoryPool用于在执行rdd的cache操作时,使用的内存.

 

Executor执行时的内存分配

这个操作通常是在task执行shuffle操作时,计算spill时,在内存中的CACHE时使用的内存.通过调用实例中的acquireExecutionMemory函数来申请内存.

override private[memory] def acquireExecutionMemory(
    numBytes: Long,
    taskAttemptId: Long,
    memoryMode: MemoryMode): Long = synchronized {

这个函数传入的memoryMode可选择是使用堆内存还是直接使用本地内存,默认是使用堆内存.
  assert(onHeapExecutionMemoryPool.poolSize + 

       storageMemoryPool.poolSize == maxMemory)
  assert(numBytes >= 0)
  memoryMode match {
    case MemoryMode.ON_HEAP =>

这里定义的这个函数,用于判断numBytes(需要申请的内存大小)减去当前内存池中可用的内存大小是否够用,如果不够用,这个函数的传入值是一个正数
      def maybeGrowExecutionPool(extraMemoryNeeded: Long): Unit = {
        if (extraMemoryNeeded > 0) {

这里根据当前的rdd的cache中的内存池的大小,减去配置的storage的存储大小,与当前storage的内存池中的可用大小,取最大值出来,这个值表示是一个可用于回收的内存资源.
          val memoryReclaimableFromStorage =
            math.max(storageMemoryPool.memoryFree

            storageMemoryPool.poolSize - storageRegionSize)
          if (memoryReclaimableFromStorage > 0) {

首先根据计算出来的storage中可以进行回收的资源,通过StorageMemoryPool进行资源的释放.得到一个完成释放的资源大小.这里根据executor中task需要的内存与storage可回收的资源取最小值进行资源的回收.把得到的可用资源添加到executor的内存池中.
            // Only reclaim as much space as is necessary and available:
            val spaceReclaimed = storageMemoryPool.shrinkPoolToFreeSpace(
              math.min(extraMemoryNeededmemoryReclaimableFromStorage))
            onHeapExecutionMemoryPool.incrementPoolSize(spaceReclaimed)
          }
        }
      }
这个函数用于计算executor的内存池可以使用的最大内存大小.最小可以使用总内存减去storage的配置权重,也就是默认情况下,shuffle的executor的内存最小可以使用0.5的权重的内存.
      def computeMaxExecutionPoolSize(): Long = {
        maxMemory - math.min(storageMemoryUsedstorageRegionSize)
      }
执行内存的分配操作.
      onHeapExecutionMemoryPool.acquireMemory(
        numBytestaskAttemptIdmaybeGrowExecutionPool

        computeMaxExecutionPoolSize)

    case MemoryMode.OFF_HEAP =>
      offHeapExecutionMemoryPool.acquireMemory(numBytestaskAttemptId)
  }
}

 

给executor中的task分配需要的内存:

private[memory] def acquireMemory(
    numBytes: Long,
    taskAttemptId: Long,
    maybeGrowPool: Long => Unit = (additionalSpaceNeeded: Long) => Unit,
    computeMaxPoolSize: () => Long = () => poolSize): Long = lock.synchronized {
  assert(numBytes > 0s"invalid number of bytes requested: $numBytes")

  // TODO: clean up this clunky method signature

  if (!memoryForTask.contains(taskAttemptId)) {

这里首先检查这个task是否在memoryForTask中存在,如果不存在时,表示这个task是第一次申请内存,在这个集合中设置此task的当前使用内存为0,并唤醒所有的当前的executor的等待的task.
    memoryForTask(taskAttemptId) = 0L
    // This will later cause waiting tasks to wake up and check numTasks again
    lock.notifyAll()
  }

  // TODO: simplify this to limit each task to its own slot
  while (true) {

执行内存的分配操作,这个操作会一直进行迭代,直到满足一定的条件.

首先得到当前的executor中有申请内存的task的个数,并得到当前的task的使用内存量.
    val numActiveTasks = memoryForTask.keys.size
    val curMem = memoryForTask(taskAttemptId)

计算出需要申请的内存与当前内存池中的内存,是否需要对storage中的内存进行回收.如果需要申请的内存大于了当前内存池的内存,这个参数传入为一个大于0的数,这个时候会对storage的内存进行回收.
    maybeGrowPool(numBytes - memoryFree)
这里计算出executor的内存值可以使用的最大内存,默认情况下,最小可使用内存为总内存减去storage的配置内存.也就是默认可使用50%的内存.
    val maxPoolSize = computeMaxPoolSize()

这里计算出每个task平均可使用的最大内存大小,与最小内存大小.

如:有5个task,可使用100MB的内存,那么最大可使用的内存为20MB,最小可使用的内存为10MB.
    val maxMemoryPerTask = maxPoolSize / numActiveTasks
    val minMemoryPerTask = poolSize / (* numActiveTasks)
这里计算出当前可申请的内存.能够申请的内存总量不能超过平均每个task使用内存的平均大小.
    // How much we can grant this task; keep its share within 0 <= X <= 1 / numActiveTasks
    val maxToGrant = math.min(numBytesmath.max(0maxMemoryPerTask - curMem))
   
    val toGrant = math.min(maxToGrantmemoryFree)

这里控制迭代是否可以跳出的条件.如果可申请的内存小于需要申请的内存,同时当前task使用的内存加上可申请的内存小于每个task平均使用的内存时,这个申请操作会wait住.等待其它的task资源回收时进行唤醒.否则跳出迭代,返回可申请的内存.
    if (toGrant < numBytes && curMem + toGrant < minMemoryPerTask) {
      logInfo(s"TID $taskAttemptId waiting for at least 1/2N of 

            $poolName pool to be free")
      lock.wait()
    } else {
      memoryForTask(taskAttemptId) += toGrant
      return toGrant

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值