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(conf, numUsableCores)

  } else {

    UnifiedMemoryManager(conf, numUsableCores)

  }

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的内存权重,生成两个内存池storageMemoryPool与onHeapExecutionMemoryPool.

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)

)

  memoryMode match {

    case MemoryMode.ON_HEAP =>

这里定义的这个函数,用于推断numBytes(须要申请的内存大小)减去当前内存池中可用的内存大小是否够用,假设不够用,这个函数的传入值是一个正数

      def maybeGrowExecutionPool(extraMemoryNeeded: Long): Unit = {

) {

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

          val memoryReclaimableFromStorage =

            math.max(storageMemoryPool.memoryFree,

storageMemoryPool.poolSize - storageRegionSize)

) {

首先依据计算出来的storage中能够进行回收的资源,通过StorageMemoryPool进行资源的释放.得到一个完毕释放的资源大小.这里依据executor中task须要的内存与storage可回收的资源取最小值进行资源的回收.把得到的可用资源加入到executor的内存池中.

            // Only reclaim as much space as is necessary and available:

            val spaceReclaimed = storageMemoryPool.shrinkPoolToFreeSpace(

              math.min(extraMemoryNeeded, memoryReclaimableFromStorage))

            onHeapExecutionMemoryPool.incrementPoolSize(spaceReclaimed)

          }

        }

      }

这个函数用于计算executor的内存池能够使用的最大内存大小.最小能够使用总内存减去storage的配置权重,也就是默认情况下,shuffle的executor的内存最小能够使用0.5的权重的内存.

      def computeMaxExecutionPoolSize(): Long = {

        maxMemory - math.min(storageMemoryUsed, storageRegionSize)

      }

运行内存的分配操作.

      onHeapExecutionMemoryPool.acquireMemory(

        numBytes, taskAttemptId, maybeGrowExecutionPool,

computeMaxExecutionPoolSize)



    case MemoryMode.OFF_HEAP =>

      offHeapExecutionMemoryPool.acquireMemory(numBytes, taskAttemptId)

  }

}

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

private[memory] def acquireMemory(

    numBytes: Long,

    taskAttemptId: Long,

    maybeGrowPool: Long => Unit = (additionalSpaceNeeded: Long) => Unit,

    computeMaxPoolSize: () => Long = () => poolSize): Long = lock.synchronized {

, s"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 / (2 * numActiveTasks)

这里计算出当前可申请的内存.可以申请的内存总量不能超过平均每一个task使用内存的平均大小.

    // How much we can grant this task; keep its share within 0 <= X <= 1 / numActiveTasks

, maxMemoryPerTask - curMem))

   

    val toGrant = math.min(maxToGrant, memoryFree)

这里控制迭代能否够跳出的条件.假设可申请的内存小于须要申请的内存,同一时候当前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

    }

  }

  0L  // Never reached

}

在BLOCK的CACHE时的内存分配

在运行rdd的iterator操作时,假设对rdd运行过cache或者persist的操作时,也就是storage的级别不是none时,会对数据进行cache操作.在cache后的block中有一个超时时间,这个超时时间在blockManager中通过一个定时器,会定时去删除cache的block与的Broadcast数据.

假设是BLOCK的CACHE的超时,可通过spark.cleaner.ttl.BLOCK_MANAGER配置.

在对RDD运行cache操作时,终于会调用内存管理器中的acquireStorageMemory函数来进行操作.

override def acquireStorageMemory(

    blockId: BlockId,

    numBytes: Long,

    evictedBlocks: mutable.Buffer[(BlockId, BlockStatus)])

: Boolean = synchronized {

这个传入的參数中,evictedBlocks是一个用于返回的传入參数,这个集合中表示进行这次申请后,被淘汰掉的block的信息.

  assert(onHeapExecutionMemoryPool.poolSize

+ storageMemoryPool.poolSize == maxMemory)

)

假设当前的BLOCK的的CACHE的大小已经超过了当前可用的内存总量(总内存减去executor的使用内存)时,直接返回false,表示不做内存分配.申请的内存太多,不处理了.

  if (numBytes > maxStorageMemory) {

    // Fail fast if the block simply won't fit

    logInfo(s"Will not store $blockId as the required space

($numBytes bytes) exceeds our " +

      s"memory limit ($maxStorageMemory bytes)")

    return false

  }

假设当前申请的block须要的内存大小超过了当前storage的内存池可用的内存大小时,在executor的内存池中回收部分资源,原则是假设申请的内存小于executor内存池可用的内存,回收申请的大小,否则回收executor全部的可用内存.并运行内存的分配操作.

  if (numBytes > storageMemoryPool.memoryFree) {

    // There is not enough free memory in the storage pool,

//    so try to borrow free memory from

    // the execution pool.

    val memoryBorrowedFromExecution =

Math.min(onHeapExecutionMemoryPool.memoryFree, numBytes)

    onHeapExecutionMemoryPool.decrementPoolSize(memoryBorrowedFromExecution)

    storageMemoryPool.incrementPoolSize(memoryBorrowedFromExecution)

  }

  storageMemoryPool.acquireMemory(blockId, numBytes, evictedBlocks)

}

在StorageMemoryPool中运行block的内存分配:

def acquireMemory(

    blockId: BlockId,

    numBytesToAcquire: Long,

    numBytesToFree: Long,

    evictedBlocks: mutable.Buffer[(BlockId, BlockStatus)])

: Boolean = lock.synchronized {

第二个參数是须要申请的内存,第三个參数假设是0表示可用内存大于申请内存,大于0表示可用内存不够用.

)

)

  assert(memoryUsed <= poolSize)

这里,假设可用的内存不够用时,通过MemoryStore中的evictBlocksToFreeSpace函数来对当前的cache进行淘汰.

) {

    memoryStore.evictBlocksToFreeSpace(Some(blockId), numBytesToFree,

evictedBlocks)

    // Register evicted blocks, if any, with the active task metrics

    Option(TaskContext.get()).foreach { tc =>

      val metrics = tc.taskMetrics()

      val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId,

BlockStatus)]())

      metrics.updatedBlocks = Some(lastUpdatedBlocks ++ evictedBlocks.toSeq)

    }

  }

假设完毕资源的回收后,当前可用的内存大于要申请的内存,表示申请成功,返回的值为true,否则为false.

  // NOTE: If the memory store evicts blocks,

//   then those evictions will synchronously call

  // back into this StorageMemoryPool in order to free memory. Therefore,

//     these variables

  // should have been updated.

  val enoughMemory = numBytesToAcquire <= memoryFree

  if (enoughMemory) {

    _memoryUsed += numBytesToAcquire

  }

  enoughMemory

}

Block的cache的淘汰

在executor的内存池不够使用时,或者总的内存不够使用时,会运行storage的内存池的资源回收操作.由shrinkPoolToFreeSpace函数,这个函数通过调用MemoryStorage中的evictBlocksToFreeSpace函数来进行block的淘汰(假设是对block的cache时,申请内存不够时会直接调用这个函数来淘汰老的block)

shrinkPoolToFreeSpace函数用于在executor的内存不够时,须要storage的内存池释放资源给executor使用时调用.

这个过程中,可给executor提供的内存分为五种可能:

1,storage默认的内存空间还没有使用完毕,同一时候executor须要的空间小于等于storage的内存池的可用空间,直接在storage的内存池中释放须要的大小.

2,storage默认的内存空间还没有使用完毕,同一时候executor须要的空间大于storage的内存池的可用空间,这个时候storage的可用空间所有进行释放,但这个时候不会做block的淘汰操作.

3,storage的默认的内存空间使用完毕,这个时候storage的内存池比默认的storage的配置权重要多,同一时候executor须要申请的内存小于多出的部分,对storage内存池中的block进行淘汰直到够executor的申请内存结束,这个时候storage的使用内存还是大于storage的默认配置权重大小.

4,storage的默认的内存空间使用完毕,这个时候storage的内存池比默认的storage的配置权重要多,同一时候executor须要申请的内存大于或等于多出的部分,对storage内存池中的block进行淘汰直到但最多仅仅淘汰到storage的配置权重大小就结束淘汰.

5,storage刚好使用到了配置的权重,无法进行分配.

def shrinkPoolToFreeSpace(spaceToFree: Long): Long = lock.synchronized {

首先依据须要释放的内存,1=executor申请的内存,2-1=storage内存池可用的内存,2-2=storage中占用的内存大于默认给storage分配的权重.

这里依据这个要释放的资源与内存池可用的资源取最小值进行释放,假设申请的小于可用的,不会对block进行淘汰操作,否则对block进行淘汰操作,直接淘汰到可用的内存空间结束.

  // First, shrink the pool by reclaiming free memory:

  val spaceFreedByReleasingUnusedMemory = math.min(spaceToFree, memoryFree)

  decrementPoolSize(spaceFreedByReleasingUnusedMemory)

  val remainingSpaceToFree = spaceToFree - spaceFreedByReleasingUnusedMemory

) {

    // If reclaiming free memory did not adequately shrink the pool,

//      begin evicting blocks:

    val evictedBlocks = new ArrayBuffer[(BlockId, BlockStatus)]

    memoryStore.evictBlocksToFreeSpace(None, remainingSpaceToFree, evictedBlocks)

    val spaceFreedByEviction = evictedBlocks.map(_._2.memSize).sum

    // When a block is released, BlockManager.dropFromMemory()

//      calls releaseMemory(), so we do

    // not need to decrement _memoryUsed here. However, we do need to decrement the

//      pool size.

    decrementPoolSize(spaceFreedByEviction)

    spaceFreedByReleasingUnusedMemory + spaceFreedByEviction

  } else {

    spaceFreedByReleasingUnusedMemory

  }

}

evictBlocksToFreeSpace函数这个函数用于对storage的内存空间中释放掉部分block的存储空间的函数,由MemoryStorage进行实现.

这个函数的三个传入參数中:

第一个在block的cache时,会传入blockid,假设是executor要求释放时,传入为None,这个參数用于控制释放的资源,假设传入了blockid,那么这个block相应的rdd的全部的全部的CACHE都会被保留,仅仅释放其他的RDD相应的BLOCK的CACHE,假设传入为None时,不区分BLOCK,从头開始迭代,直接释放到须要的内存大小结束.

第二个是须要释放的内存大小.

第三个參数是释放后的block的集合,这个集合内容就是从内存中淘汰出去的block.

private[spark] def evictBlocksToFreeSpace(

    blockId: Option[BlockId],

    space: Long,

    droppedBlocks: mutable.Buffer[(BlockId, BlockStatus)]): Boolean = {

)

  memoryManager.synchronized {

    var freedMemory = 0L

这里得到传入的blockid相应的rdd.假设传入的blockId是None时,这个rdd也就不存在.

    val rddToAdd = blockId.flatMap(getRddId)

    val selectedBlocks = new ArrayBuffer[BlockId]

    // This is synchronized to ensure that the set of entries is not changed

    // (because of getValue or getBytes) while traversing the iterator, as that

    // can lead to exceptions.

    entries.synchronized {

      val iterator = entries.entrySet().iterator()

这里从全部的cache的block中进行迭代,假设迭代的block的rdd不是如今须要cache的block相应的rdd时(传入的blockId相应的RDD),就选择这个block.并释放内存大小.

      while (freedMemory < space && iterator.hasNext) {

        val pair = iterator.next()

        val blockId = pair.getKey

        if (rddToAdd.isEmpty || rddToAdd != getRddId(blockId)) {

          selectedBlocks += blockId

          freedMemory += pair.getValue.size

        }

      }

    }



    if (freedMemory >= space) {

      logInfo(s"${selectedBlocks.size} blocks selected for dropping")

      for (blockId <- selectedBlocks) {

这里对选择的block,通过blockManager释放掉block的cache占用的内存.假设这个block的cache的级别中包括有disk的级别时,释放掉内存的同一时候会把这个cache的数据写入到磁盘中.

把运行释放后的block的集合加入到传入參数的droppedBlocks的集合參数中,用于数据的返回.

val entry = entries.synchronized { entries.get(blockId) }

        // This should never be null as only one task should be dropping

        // blocks and removing entries. However the check is still here for

        // future safety.

        if (entry != null) {

          val data = if (entry.deserialized) {

            Left(entry.value.asInstanceOf[Array[Any]])

          } else {

            Right(entry.value.asInstanceOf[ByteBuffer].duplicate())

          }

          val droppedBlockStatus = blockManager.dropFromMemory(blockId, data)

          droppedBlockStatus.foreach { status => droppedBlocks +=

((blockId, status)) }

        }

      }

      true

    } else {

      blockId.foreach { id =>

        logInfo(s"Will not store $id as it would require dropping another block " +

          "from the same RDD")

      }

      false

    }

  }

}

StaticMemoryManager

这个实例在1.6的环境下,须要把配置项spark.memory.useLegacyMode设置为true时,才会被启用.以下首先先看看这个实例生成时的处理:

须要的配置项:

1,配置项spark.shuffle.memoryFraction,用于设置executor的shuffle操作可使用的内存,默认占总内存的0.2.

2,配置项spark.shuffle.safetyFraction,用于设置executor的shuffle的安全操作内存,默认占1配置内存的0.8.

3,配置项spark.storage.memoryFraction,用于设置block cache的使用内存,默认占总内存的0.6;

4,配置项spark.storage.safetyFraction,用于设置block cache的安全使用内存,默认占3配置内存的0.9;

5,配置项spark.storage.unrollFraction,默认值是storage内存总大于的0.2;这个有于在storage中cache的block的数据的反序列化时数据的展开使用空间.

def this(conf: SparkConf, numCores: Int) {

  this(

    conf,

    StaticMemoryManager.getMaxExecutionMemory(conf),

    StaticMemoryManager.getMaxStorageMemory(conf),

    numCores)

}

在Executor运行时的内存分配

在StaticMemoryManager中,对executor中的shuffle的内存运行分配这块事实上并没有统一内存管理中那么麻烦,仅仅是在分配的固定大小的存储空间中进行分配,假设无法再进行分配时,这个分配函数返回的分配量就是0.

private[memory] override def acquireExecutionMemory(

    numBytes: Long,

    taskAttemptId: Long,

    memoryMode: MemoryMode): Long = synchronized {

OFF_HEAP的模式这里就不分析了,我在代码里没发现有地方去调用,好像申请内存时,是直接写死的ON_HEAP的模式.在这个地方,不会考虑executor的内存池中的内存是否够用,直接通过ExecutionMemoryPool内存池实例中的分配内存函数进行内存的分配 .

memoryMode match {

    case MemoryMode.ON_HEAP => onHeapExecutionMemoryPool.acquireMemory(numBytes,

taskAttemptId)

    case MemoryMode.OFF_HEAP => offHeapExecutionMemoryPool.acquireMemory(numBytes,

taskAttemptId)

  }

}

内存分配部分的代码实现:这个部分与统一内存管理部分是一样的,

private[memory] def acquireMemory(

    numBytes: Long,

    taskAttemptId: Long,

    maybeGrowPool: Long => Unit = (additionalSpaceNeeded: Long) => Unit,

    computeMaxPoolSize: () => Long = () => poolSize): Long = lock.synchronized {

, s"invalid number of bytes requested: $numBytes")



  // TODO: clean up this clunky method signature



首先假设说task是第一次申请内存,加入这个task到内存池的集合属性中,并把这个task的使用内存设置为0.  

  if (!memoryForTask.contains(taskAttemptId)) {

    memoryForTask(taskAttemptId) = 0L

    // This will later cause waiting tasks to wake up and check numTasks again

    lock.notifyAll()

  }



  以下開始迭代进行内存的分配.加上while的目的是为了保持假设task的分配内存达不到指定的大小时,就一直等待分配,直到达到指定的大小.

  // TODO: simplify this to limit each task to its own slot

  while (true) {

    val numActiveTasks = memoryForTask.keys.size

    val curMem = memoryForTask(taskAttemptId)

在这里,这个函数是一个空的实现,什么都不会做.

    maybeGrowPool(numBytes - memoryFree)

这个函数得到的值就是当前的executor的内存池的poolsize的大小.

    val maxPoolSize = computeMaxPoolSize()

依据当前的活动的task的个数计算出每一个task可使用的最大内存,每一个task使用的最小内存为最大内存除以2(假设申请的内存本身小于这个最小内存除外).

    val maxMemoryPerTask = maxPoolSize / numActiveTasks

    val minMemoryPerTask = poolSize / (2 * numActiveTasks)

计算出这次须要分配的内存,假设申请的内存小于可用的内存时,取申请内存,否则取这个task可申请的最大内存

    // How much we can grant this task; keep its share within 0 <= X <= 1 / numActiveTasks

, maxMemoryPerTask - curMem))

这里计算出来的值依据当前原则上能够申请的内存与当前内存池中的可用内存取最小值.

    val toGrant = math.min(maxToGrant, memoryFree)

这里有一个线程wait的条件,假设这一次申请的内存小于须要申请的内存,同一时候当前的task的使用内存小于最小的使用内存时,线程wait,等待其他的task释放内存或者有新的task增加来唤醒此wait.

    // We want to let each task get at least 1 / (2 * numActiveTasks) before blocking;

    // if we can't give it this much now, wait for other tasks to free up memory

    // (this happens if older tasks allocated lots of memory before N grew)

    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

    }

  }

  0L  // Never reached

}

Storage的展开内存分配

这里说明下,在UnifiedMemoryManager中展开内存的分配与stroage中block cache的内存分配共用同样的内存空间,因此申请方法与storage的block cache的内存分配同样,而在static的分配中,不同的区块,所使用的内存空间都是固定的,因此这里须要独立说明一下.

在对MemoryStorage中运行block的cache操作时,会运行pubInterator等操作,会先依据block中的数据申请相应数据大小的展开内存空间,把数据进行提取,然后才会运行storage的cache操作的内存分配.

运行流程,在MemoryStorage中:

1,putIterator函数运行,对block进行cache

2,unrollSafely函数运行,申请展开内存,依据block的内容大小.

3,释放申请的展开内存,并申请block cache内存,运行putArray->tryToPut函数.

看看unrollSafely函数怎样处理展开内存的申请:

这里先配置项spark.storage.unrollMemoryThreshold,默认值是1MB,先申请固定大小的展开内存,这个函数返回的值是一个true/false的值,true表示申请成功.这个函数调用内存管理器中的acquireUnrollMemory函数.这里申请到的内存大小会先记录到unrollMemoryMap集合中依据相应的taskid.

keepUnrolling = reserveUnrollMemoryForThisTask(blockId, initialMemoryThreshold,

droppedBlocks)

接下来,迭代block中的数据,把数据加入到vector的展开暂时变量中.

while (values.hasNext && keepUnrolling) {

  vector += values.next()

) {

    // If our vector's size has exceeded the threshold, request more memory

    val currentSize = vector.estimateSize()

    if (currentSize >= memoryThreshold) {

这里每次申请当前的使用内存的一半做为展开内存,这个展开内存伴随着block的数据越多,申请的量也会越大.

      val amountToRequest = (currentSize * memoryGrowthFactor -

memoryThreshold).toLong

      keepUnrolling = reserveUnrollMemoryForThisTask(

        blockId, amountToRequest, droppedBlocks)

      // New threshold is currentSize * memoryGrowthFactor

      memoryThreshold += amountToRequest

    }

  }



}

这里的推断比較关键,假设keepUnrolling的值为true,表示内存可以安全展开这个block的数据,否则表示不能展开这个block的内容.

if (keepUnrolling) {

  // We successfully unrolled the entirety of this block

  Left(vector.toArray)

} else {

  // We ran out of space while unrolling the values for this block

  logUnrollFailureMessage(blockId, vector.estimateSize())

  Right(vector.iterator ++ values)

}

if (keepUnrolling) {

  val taskAttemptId = currentTaskAttemptId()

  memoryManager.synchronized {

这里假设内存可以安全展开当前的block,把这个block的展开内存存储到pendingUnrollMemoryMap的集合中相应此task的位置.

    // Since we continue to hold onto the array until we actually cache it, we cannot

    // release the unroll memory yet. Instead, we transfer it to pending unroll memory

    // so `tryToPut` can further transfer it to normal storage memory later.

    // TODO: we can probably express this without pending unroll memory (SPARK-10907)

    val amountToTransferToPending = currentUnrollMemoryForThisTask -

previousMemoryReserved

    unrollMemoryMap(taskAttemptId) -= amountToTransferToPending

    pendingUnrollMemoryMap(taskAttemptId) =

      pendingUnrollMemoryMap.getOrElse(taskAttemptId, 0L) +

amountToTransferToPending

  }

}

提示:关于展开内存的释放部分,假设block的内容可以被安全展开存储到内存中时,这个时候,在做block的storage的操作时,会释放掉展开内存的空间(在pendingUnrollMemoryMap集合中),假设内存不可以安全展开block的内容时,这个时候无法进行block的cache操作(可能会写磁盘),这时申请的内容大小存储在unrollMemoryMap集合中,这时因为不会运行block的memory的cache操作,因此这个集合中占用的内存大小临时不会被回收,仅仅有等到这个task结束时,占用的unrollMemoryMap集合中的内存才会被回收.

...

接下来看看在StaticMemoryManager中怎样处理展开内存的分配:

override def acquireUnrollMemory(

    blockId: BlockId,

    numBytes: Long,

    evictedBlocks: mutable.Buffer[(BlockId, BlockStatus)])

: Boolean = synchronized {

  val currentUnrollMemory = storageMemoryPool.memoryStore.currentUnrollMemory

  val freeMemory = storageMemoryPool.memoryFree

这里依据可用的最大展开内存与当前正在使用中的展开内存,计算出能够申请的最大展开内存,假设这里得到的值是一个0时,表示不须要释放block cache的内存,假设是一个大于0的值,就表示须要释放BLOCK CACHE的内存.

  // When unrolling, we will use all of the existing free memory, and, if necessary,

  // some extra space freed from evicting cached blocks. We must place a cap on the

  // amount of memory to be evicted by unrolling, however, otherwise unrolling one

  // big block can blow away the entire cache.

, maxUnrollMemory - currentUnrollMemory -

freeMemory)

这里计算出须要释放的内存,取申请的资源与能够使用的unroll内存资源的最小值,假设这个一个大于0的值,表示须要从storage的内存池中释放这么多的内存出来.

  // Keep it within the range 0 <= X <= maxNumBytesToFree

, math.min(maxNumBytesToFree,

numBytes - freeMemory))

  storageMemoryPool.acquireMemory(blockId, numBytes, numBytesToFree,

evictedBlocks)

}

Storage中block cache的内存分配

在block使用了memory的storage时,同一时候block的内容可以被展开内存存储起来时,会通过MemoryStorage中相应的函数来向StaticMemoryManager中的acquireStorageMemory函数申请内存资源.

override def acquireStorageMemory(

    blockId: BlockId,

    numBytes: Long,

    evictedBlocks: mutable.Buffer[(BlockId, BlockStatus)])

: Boolean = synchronized {

  if (numBytes > maxStorageMemory) {

假设一个block的内容太大,已经超过了配置的storage的存储空间大小,这个block不做cache.

    // Fail fast if the block simply won't fit

    logInfo(s"Will not store $blockId as the required space

($numBytes bytes) exceeds our " +

      s"memory limit ($maxStorageMemory bytes)")

    false

  } else {

否则通过storage的内存池运行block的cache的内存申请,这个过程中假设内存不够用时,会释放老的block的cache相应的内存空间,也就是会淘汰掉老的block cache,

    storageMemoryPool.acquireMemory(blockId, numBytes, evictedBlocks)

  }

}

在storage的内存池中处理block cache的内存申请:

def acquireMemory(

    blockId: BlockId,

    numBytesToAcquire: Long,

    numBytesToFree: Long,

    evictedBlocks: mutable.Buffer[(BlockId, BlockStatus)])

: Boolean = lock.synchronized {

这个函数的传入參数中,numBytesToAcquire表示须要申请的内存大小,numBytesToFree假设是一个大于0的值,表示如今内存池中的内存空间不够,须要淘汰现有的block的cache.

)

)

  assert(memoryUsed <= poolSize)

这里先推断,假设申请的内存大于还在可用的内存,须要先淘汰掉部分block cache来释放空间.

) {

    memoryStore.evictBlocksToFreeSpace(Some(blockId), numBytesToFree,

evictedBlocks)

    // Register evicted blocks, if any, with the active task metrics

    Option(TaskContext.get()).foreach { tc =>

      val metrics = tc.taskMetrics()

      val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId,

BlockStatus)]())

      metrics.updatedBlocks = Some(lastUpdatedBlocks ++ evictedBlocks.toSeq)

    }

  }

分配内存是否成功,也就是要申请的内存小于或等于可用的内存空间,最后把分配的内存加入到使用的内存空间中.表示这部分内存已经被look住.

  val enoughMemory = numBytesToAcquire <= memoryFree

  if (enoughMemory) {

    _memoryUsed += numBytesToAcquire

  }

  enoughMemory

}

Storage处理block cache的淘汰

在storage中内存不够使用时,通过memoryStorage去运行block的淘汰,并把淘汰后的block返回通知上层的调用端.

) {

  memoryStore.evictBlocksToFreeSpace(Some(blockId), numBytesToFree, evictedBlocks)

  // Register evicted blocks, if any, with the active task metrics

  Option(TaskContext.get()).foreach { tc =>

    val metrics = tc.taskMetrics()

    val lastUpdatedBlocks = metrics.updatedBlocks.getOrElse(Seq[(BlockId,

BlockStatus)]())

    metrics.updatedBlocks = Some(lastUpdatedBlocks ++ evictedBlocks.toSeq)

  }

}

MemoryStore中处理对cache的淘汰:

对block的cache进行淘汰的处理函数,传入參数中,第二个參数是须要释放的空间,第三个參数是被淘汰后的block的集合用于返回.

private[spark] def evictBlocksToFreeSpace(

    blockId: Option[BlockId],

    space: Long,

    droppedBlocks: mutable.Buffer[(BlockId, BlockStatus)]): Boolean = {

)

  memoryManager.synchronized {

    var freedMemory = 0L

这里首先先得到传入的block相应的rdd的id.得到这个rdd_id的目的是淘汰block时,假设发现是这个rdd的block时,不进行淘汰.

    val rddToAdd = blockId.flatMap(getRddId)

    val selectedBlocks = new ArrayBuffer[BlockId]

    // This is synchronized to ensure that the set of entries is not changed

    // (because of getValue or getBytes) while traversing the iterator, as that

    // can lead to exceptions.

    entries.synchronized {

这里開始对storage的内存中全部的cache进行迭代,这个迭代从最先进行cache的block開始,假设迭代到的block相应的RDD不是传入的BLOCK相应的RDD时,把这个BLOCK加入到选择的BLOCK的集合中,并计算当前内存池中的内存是否达到须要的内存空间,假设达到,停止选择BLOCK的操作.

      val iterator = entries.entrySet().iterator()

      while (freedMemory < space && iterator.hasNext) {

        val pair = iterator.next()

        val blockId = pair.getKey

        if (rddToAdd.isEmpty || rddToAdd != getRddId(blockId)) {

          selectedBlocks += blockId

          freedMemory += pair.getValue.size

        }

      }

    }



    if (freedMemory >= space) {

假设流程运行到这里,说明内存空间释放成功,如今可用的内存空间已经达到须要的内存空间的大小,把选择的BLOCK相应的CACHE通过BLOCKMANAGER从内存中进行释放.并把释放后的BLOCK加入到droppedBlocks的集合中,这个集合用于返回结果,表示这次空间的释放时,这些BLOCK已经从CACHE中称出.

      logInfo(s"${selectedBlocks.size} blocks selected for dropping")

      for (blockId <- selectedBlocks) {

        val entry = entries.synchronized { entries.get(blockId) }

        // This should never be null as only one task should be dropping

        // blocks and removing entries. However the check is still here for

        // future safety.

        if (entry != null) {

          val data = if (entry.deserialized) {

            Left(entry.value.asInstanceOf[Array[Any]])

          } else {

            Right(entry.value.asInstanceOf[ByteBuffer].duplicate())

          }

          val droppedBlockStatus = blockManager.dropFromMemory(blockId, data)

          droppedBlockStatus.foreach { status => droppedBlocks += ((blockId,

status)) }

        }

      }

      true

    } else {

流程运行到这里,表示STORAGE的内存空间中无法释放出很多其它的内存,也就相当于是释放空间失败求.

      blockId.foreach { id =>

        logInfo(s"Will not store $id as it would require dropping another block " +

          "from the same RDD")

      }

      false

    }

  }

}

spark的内存分配管理的更多相关文章

  1. Yarn 内存分配管理机制及相关参数配置

    上一篇hive on tez 任务报错中提到了containter内存不足,现对yarn 内存分配管理进行介绍 一.相关配置情况 关于Yarn内存分配与管理,主要涉及到了ResourceManage. ...

  2. tcmalloc jemalloc glibc内存分配管理模块性能测试对比

    tcmalloc是谷歌提供的内存分配管理模块 jemalloc是FreeBSD提供的内存分配管理模块 glibc是Linux提供的内存分配管理模块 并发16个线程,分配压测3次,每次压15分钟,可以看 ...

  3. C/C++语言学习——内存分配管理

    1.一个由C编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈.程序结束时由编译器自动释放. ...

  4. redis内存分配管理与集群环境下Session管理

    ##################内存管理############### 1.Redis的内存管理 .与memcache不同,没有实现自己的内存池 .在2..4以前,默认使用标准的内存分配函数(li ...

  5. 运行时数据区即内存分配管理——JVM之六

    内存分配结构,请参考: http://iamzhongyong.iteye.com/blog/1333100

  6. RAMCloud:内存云存储的内存分配机制

    现在全闪存阵列已经见怪不怪了,EMC的XtremIO,还有VNX-F(Rockies),IBM FlashSystem.全闪存真正为效率而生,重新定义存储速度.凭借极致性能,高可用性,为您极大提高企业 ...

  7. 模拟linux的内存分配与回收

    模拟linux的内存分配与回收 要求 通过深入理解内存分配管理的三种算法,定义相应的数据结构,编写具体代码. 充分模拟三种算法的实现过程,并通过对比,分析三种算法的优劣. (1)掌握内存分配FF,BF ...

  8. ptmalloc,tcmalloc和jemalloc内存分配策略研究 ? I'm OWen..

    转摘于http://www.360doc.com/content/13/0915/09/8363527_314549949.shtml 最近看了glibc的ptmaoolc,Goolge的tcmall ...

  9. Go语言内存分配(简述 转)

    在Go语言里,从内存的分配到不再使用后内存的回收等等这些内存管理工作都是由Go在底层完成的.虽然开发者在写代码时不必过度关心内存从分配到回收这个过程,但是Go的内存分配策略里有不少有意思的设计,通过了 ...

随机推荐

  1. vue svg的使用

    项目要求: 需要把websocket推送的数据进行展示.不停地刷掉旧的数据.但是需要根据数据坐标圈出来对应的车辆. 开始使用的是canvas进行画图,思路是使用absolute定位,for循环,在图片 ...

  2. CSDN开博一周年--总结、感想和未来规划

    2012年9月22日,我在CSDN发表了第1篇博文-为了忘却的纪念,我的天龙游戏生涯.本文讲述了我大学期间玩网络游戏-天龙八部的故事. 在大学期间,实际上我也有自己的帐号-huoyingfans,主要 ...

  3. Ubuntu(Linux Mint):sudo apt-get upgrade升级失败

    Ubuntu上进行sudo apt-get upgrade后出现异常,升级失败. 异常信息如下: E: dpkg was interrupted, you must manually run 'dpk ...

  4. Eureka Server添加用户认证

    Eureka Server添加用户认证 学习了:http://blog.csdn.net/liuchuanhong1/article/details/54729556 注意:1,需要使用 defaul ...

  5. Android LaunchMode案例篇

    首先感谢小伙伴的关注.然后祝愿广大的情侣们节日快乐! 在开发中有时会遇到这种场景,用户点击注冊.第一步,第二步,完毕注冊跳转到登录界面,不须要用户一步一步的返回到登录界面.这是怎么实现的呢? 案例:有 ...

  6. web forms page和control的生命周期life cycle交互,以及page生命周期中每个event中需要做什么事情

    只有 page_load和page_init这些可以autoeventwireup RenderControl只提供override public override void RenderContro ...

  7. ES API 备忘

    本文所列的所有API在ElasticSearch文档是有详尽的说明,但它的结构组织的不太好. 这篇文章把ElasticSearch API用表格的形式供大家参考. https://www.iteblo ...

  8. Effective Java(一)—— 创建和销毁对象

    在客户端(调用端)获取自身实例的方法: 公有的构造器: 类的静态工厂方法: 1. 使用静态工厂方法代替构造器 Boolean 是对基本类型 boolean 的包装类: public final cla ...

  9. Python入门机器学习

    如何通过Python入门机器学习 我们都知道机器学习是一门综合性极强的研究课题,对数学知识要求很高.因此,对于非学术研究专业的程序员,如果希望能入门机器学习,最好的方向还是从实践触发. 我了解到Pyt ...

  10. Java 以空格分割字符串

    Java中使用类似于 str.split("/"); 的方法可以很方便的实现将一个字符串分割为数组, 但是如果分隔符是一个(或几个)空格呢?? 我们的本能反应或许应该是 str.s ...