MemoryManager-内存管理模型分析
1.概述
本次分析基于spark版本2.11进行;
2.内存管理模型实例化入口
说明:
- SparkEnv中存在内存管理模型MemoryManager的属性memoryManager,用于维护当前环境的内存管理器;
- 在创建driver端/executor端的运行环境SparkEnv时,会将需要初始化的属性进行赋值初始化,其中就包括内存管理器memoryManager;
- 在实例化内存管理模型MemoryManager时,根据参数
spark.memory.useLegacyMode
选用具体实现类进行实例化;MemoryManager
作为内存管理模型的顶级基类,其有静态内存管理器StaticMemoryManager
和统一内存管理器UnifiedMemoryManager
这2个子类;- 实例化时选择哪个子类,可以通过
spark.memory.useLegacyMode
参数进行设置,默认false,即默认选用统一内存模型UnifiedMemoryManager
作为运行环境的内存管理器;
- driver端和每个executor端,都有自己的运行环境SparkEnv,所以都会单独有自己的内存管理器;
object SparkEnv extends Logging {
private def create(
conf: SparkConf,
executorId: String,
bindAddress: String,
advertiseAddress: String,
port: Option[Int],
isLocal: Boolean,
numUsableCores: Int,
ioEncryptionKey: Option[Array[Byte]],
listenerBus: LiveListenerBus = null,
mockOutputCommitCoordinator: Option[OutputCommitCoordinator] = None): SparkEnv = {
//---------其他代码--------
//跟据参数实例化MemoryManager对象
val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)
val memoryManager: MemoryManager =
if (useLegacyMemoryManager) {
new StaticMemoryManager(conf, numUsableCores)
} else {
UnifiedMemoryManager(conf, numUsableCores)
}
//---------其他代码--------
//构建SparkEnv,实现内存管理模型memoryManager的初始化
val envInstance = new SparkEnv(
executorId,
rpcEnv,
serializer,
closureSerializer,
serializerManager,
mapOutputTracker,
shuffleManager,
broadcastManager,
blockManager,
securityManager,
metricsSystem,
memoryManager,
outputCommitCoordinator,
conf)
//---------其他代码--------
//返回构建的SparkEnv实例
envInstance
}
}
3.UnifiedMemoryManager-统一内存管理模型
说明:
- 通过
spark.executor.memory
指定executorContainer中JVM的堆内内存; - 通过
spark.yarn.executor.memoryOverhead
指定JVM的堆外内存;(途中Off-Heap-1)- 受JVM控制;
- 主要用于JVM自身,字符串, NIO Buffer等开销
- 通过
spark.memory.offHeap.enable/size
指定JVM外的堆外内存;(图中Off-Heap-2)- Spark利用TungSten技术直接操作管理JVM外的原生内存;
- 主要是为了解决Java对象开销大和GC的问题,供统一内存管理用作Execution Memory及Storage Memory的用途;
- 通过
spark.testing.memory
参数指定JVM堆内存中系统内存,默认大小Runtime.getRuntime.maxMemory
; - 通过
spark.testing.reservedMemory
指定预留内存,默认300M; - 系统内存 - 预留内存,余下的为可用内存;
- 可用内存中,通过
spark.memory.fraction
参数指定用于执行、shuffle、缓存的内存比例,默认0.6;即为统一内存(堆内最大可用内存);- 统一内存中,通过
spark.memory.storageFraction
参数指定用于存储spark的缓存数据,rdd缓存,广播变量等的存储内存;默认0.5; - 统一内存中,除开存储内存,余下的比例为用于存储shuffle,join, sort, aggregation计算的临时数据执行内存;
- 存储内存和执行内存,默认各占用统一内存的一半;
- 统一内存中,通过
- 可用内存中,除开可用内存的0.6,剩下的0.4为用户内存,用于存储用户代码生成的对象及RDD依赖;
3.1.实例化
参数说明:
spark.testing.memory
:指定系统内存,默认Runtime.getRuntime.maxMemory
;spark.testing.reservedMemory
:指定预留内存,默认300M;spark.executor.memory
:指定executorContainer中JVM的堆内内存;spark.memory.fraction
:堆内内存中用于执行、shuffle、缓存的内存比例,默认0.6;
流程说明:
- 根据预留内存计算 最小系统内存 = 预留内存 * 1.5;默认450M;
- 当系统内存 < 最小系统内存 时,抛异常提升通过请通过–driver-memory或spark.driver.memory增加系统内存;
- 当堆内执行内存executorMemory < 最小系统内存时,/抛异常提示通过–executor-memory或spark.executor.memory增加执行内存;
- 堆内存中,可用内存 = 系统内存 - 预留内存;
- 最大可用内存(统一内存):可用内存中的0.6;(通过
spark.memory.fraction
参数指定,默认0.6) - 用户内存:可用内存中的0.4;
- 统一内存中,通过
spark.memory.storageFraction
参数指定存储内存池容量所占比例,默认0.5;
object UnifiedMemoryManager {
//系统预留内存
private val RESERVED_SYSTEM_MEMORY_BYTES = 300 * 1024 * 1024
//实例化
def apply(conf: SparkConf, numCores: Int): UnifiedMemoryManager = {
//最大可用内存:存储和执行内存之和
val maxMemory = getMaxMemory(conf)
new UnifiedMemoryManager(
conf,
maxHeapMemory = maxMemory,//总的可用内存
//可用内存中用于存储的内存:默认可用内存的一半
onHeapStorageRegionSize =
(maxMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong,
//cpu核数
numCores = numCores)
}
//计算最大可用内存
private def getMaxMemory(conf: SparkConf): Long = {
//系统内存,默认Runtime.getRuntime.maxMemory
val systemMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)
//预留内存,默认300M
val reservedMemory = conf.getLong("spark.testing.reservedMemory",
if (conf.contains("spark.testing")) 0 else RESERVED_SYSTEM_MEMORY_BYTES)
//最小系统内存,默认450M(预留内存的1.5倍)
val minSystemMemory = (reservedMemory * 1.5).ceil.toLong
//系统内存不足
if (systemMemory < minSystemMemory) {
//抛异常:请通过--driver-memory或spark.driver.memory增加系统内存
throw new IllegalArgumentException(s"System memory $systemMemory must " +
s"be at least $minSystemMemory. Please increase heap size using the --driver-memory " +
s"option or spark.driver.memory in Spark configuration.")
}
//检查执行内存
if (conf.contains("spark.executor.memory")) {
//通过spark.executor.memory指定执行内存
val executorMemory = conf.getSizeAsBytes("spark.executor.memory")
//执行内存不足
if (executorMemory < minSystemMemory) {
//抛异常:请通过--executor-memory或spark.executor.memory增加执行内存
throw new IllegalArgumentException(s"Executor memory $executorMemory must be at least " +
s"$minSystemMemory. Please increase executor memory using the " +
s"--executor-memory option or spark.executor.memory in Spark configuration.")
}
}
//系统内存 - 预留内存,剩下的为可用内存
val usableMemory = systemMemory - reservedMemory
//堆内内存中用于执行、shuffle、缓存的内存比例,默认0.6
val memoryFraction = conf.getDouble("spark.memory.fraction", 0.6)
//0.6个可用内存为最大可用内存
(usableMemory * memoryFraction).toLong
}
}
3.2.UnifiedMemoryManager属性解析
说明:
- 创建统一内存管理器时,维护最大可用内存池大小,同时创建堆外、堆内的存储、执行内存池;
- 创建内存池并初始化内存池容量;
- 确定tungsten分配内存的位置:堆内/堆外;
- 确定数据存储page的默认大小;
- 初始化tungsten内存分配器;
- 验证堆内、堆外内存大小的合理性;
- 要求:堆内执行内存 + 堆内存储内存 == 堆内最大可用内存;
- 要求:JVM外堆外执行内存 + JVM外堆外存储内存 == JVM外堆外最大可用内存;
- JVM外堆外最大可用内存通过
spark.memory.offHeap.size
参数指定;
private[spark] class UnifiedMemoryManager private[memory] (
conf: SparkConf,
//指定最大可用堆内内存
val maxHeapMemory: Long,
onHeapStorageRegionSize: Long,
numCores: Int)
extends MemoryManager(//调用父类构造器
conf,
numCores,
//指定堆内可用于存储的内存
onHeapStorageRegionSize,
//指定堆内可用于执行的内存
maxHeapMemory - onHeapStorageRegionSize) {
//校验函数
private def assertInvariants(): Unit = {
//要求:堆内执行内存 + 堆内存储内存 == 堆内最大可用内存
assert(onHeapExecutionMemoryPool.poolSize + onHeapStorageMemoryPool.poolSize == maxHeapMemory)
//要求:JVM外堆外执行内存 + JVM外堆外存储内存 == JVM外堆外最大可用内存
assert(
offHeapExecutionMemoryPool.poolSize + offHeapStorageMemoryPool.poolSize == maxOffHeapMemory)
}
//内存设置校验
assertInvariants()
}
private[spark] abstract class MemoryManager(
conf: SparkConf,
numCores: Int,
onHeapStorageMemory: Long,
onHeapExecutionMemory: Long) extends Logging {
// -- Methods related to memory allocation policies and bookkeeping ------------------------------
//初始化当前统一内存管理器的堆内存储内存池
@GuardedBy("this")
protected val onHeapStorageMemoryPool = new StorageMemoryPool(this, MemoryMode.ON_HEAP)
//初始化当前统一内存管理器的JVM外堆外存储内存池
@GuardedBy("this")
protected val offHeapStorageMemoryPool = new StorageMemoryPool(this, MemoryMode.OFF_HEAP)
//初始化当前统一内存管理器的堆内执行内存池
@GuardedBy("this")
protected val onHeapExecutionMemoryPool = new ExecutionMemoryPool(this, MemoryMode.ON_HEAP)
//初始化当前统一内存管理器的JVM外堆外执行内存池
@GuardedBy("this")
protected val offHeapExecutionMemoryPool = new ExecutionMemoryPool(this, MemoryMode.OFF_HEAP)
//设置堆内存储内存池大小
onHeapStorageMemoryPool.incrementPoolSize(onHeapStorageMemory)
//设置堆内支持内存池大小
onHeapExecutionMemoryPool.incrementPoolSize(onHeapExecutionMemory)
//从配置中获取JVM外堆外最大可用内存
protected[this] val maxOffHeapMemory = conf.get(MEMORY_OFFHEAP_SIZE)
//计算JVM外堆外存储内存大小
protected[this] val offHeapStorageMemory =
(maxOffHeapMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong
//设置JVM外堆外执行内存大小
offHeapExecutionMemoryPool.incrementPoolSize(maxOffHeapMemory - offHeapStorageMemory)
//设置JVM外堆外存储内存大小
offHeapStorageMemoryPool.incrementPoolSize(offHeapStorageMemory)
//确定tungsten分配内存的位置:堆内/堆外
final val tungstenMemoryMode: MemoryMode = {
//当spark.memory.offHeap.enabled开启,spark.memory.offHeap.size > 0 时,堆外;
//否则,堆内
if (conf.get(MEMORY_OFFHEAP_ENABLED)) {
require(conf.get(MEMORY_OFFHEAP_SIZE) > 0,
"spark.memory.offHeap.size must be > 0 when spark.memory.offHeap.enabled == true")
require(Platform.unaligned(),
"No support for unaligned Unsafe. Set spark.memory.offHeap.enabled to false.")
MemoryMode.OFF_HEAP
} else {
MemoryMode.ON_HEAP
}
}
//确定数据存储页面大小,以字节为单位。
//如果用户没有显式设置spark.buffer.pageSize,我们通过查看进程可用的内核数和内存总量来得出默认值,然后除以安全系数
val pageSizeBytes: Long = {
val minPageSize = 1L * 1024 * 1024 // 1MB
val maxPageSize = 64L * minPageSize // 64MB
//可用cpu核数
val cores = if (numCores > 0) numCores else Runtime.getRuntime.availableProcessors()
//安全系数
val safetyFactor = 16
//tungsten最大可用内存
val maxTungstenMemory: Long = tungstenMemoryMode match {
case MemoryMode.ON_HEAP => onHeapExecutionMemoryPool.poolSize
case MemoryMode.OFF_HEAP => offHeapExecutionMemoryPool.poolSize
}
//大小:总内存量 / cpu核数 / 安全系统
val size = ByteArrayMethods.nextPowerOf2(maxTungstenMemory / cores / safetyFactor)
//要求: 最大值 > 默认值 > 最小值
val default = math.min(maxPageSize, math.max(minPageSize, size))
conf.getSizeAsBytes("spark.buffer.pageSize", default)
}
//初始化内存分配器
private[memory] final val tungstenMemoryAllocator: MemoryAllocator = {
tungstenMemoryMode match {
case MemoryMode.ON_HEAP => MemoryAllocator.HEAP
case MemoryMode.OFF_HEAP => MemoryAllocator.UNSAFE
}
}
}
3.3.申请执行内存
说明:
- 定义根据申请的内存模式确定获取内存池信息的逻辑;
- MemoryMode.ON_HEAP:返回堆内执行内存池信息;
- MemoryMode.OFF_HEAP:返回堆外执行内存池信息;
- 定义尝试从存储内存池借用内存的逻辑;
- 要求:计划从存储内存池借用的内存大小 > 0 且 存储内存池存在可以借用的内存;
- 存储内存池最大可借用内存大小:max(storagePool.memoryFree, storagePool.poolSize - storageRegionSize);
- 当存储内存池存在空余内存时,借用存储内存池空余内存;
- 当存储内存池无空余内存,且存储内存池从执行内存池借用内存导致内存池容量大于最开始分片的内存容量时,归还从执行内存池借用的内存;
- 实际内存借用量:min(计划借用量,可以借用量);
- 存储内存池根据实际内存借用量缩容,执行内存池扩容;
- 计算执行内存池最大可用内存:最大可用内存 - min(存储内存池已用内存大小, 存储内存池大小);
- 当存储内存池没有用完内存,剩余内存将会被包含在执行内存池最大可用内存中,存在被占用可能;
- 执行内存池根据申请内存量分配内存;
private[spark] class UnifiedMemoryManager private[memory] (
conf: SparkConf,
val maxHeapMemory: Long,
onHeapStorageRegionSize: Long,
numCores: Int)
extends MemoryManager(
conf,
numCores,
onHeapStorageRegionSize,
maxHeapMemory - onHeapStorageRegionSize) {
//申请执行内存
override private[memory] def acquireExecutionMemory(
numBytes: Long, //申请的内存大小
taskAttemptId: Long,//申请内存的task
//申请内存的模式:堆内内存/堆外内存
memoryMode: MemoryMode): Long = synchronized {
//内存设置校验
assertInvariants()
assert(numBytes >= 0)
//获取执行内存池、存储内存池、存储内存大小、最大可用内存
val (executionPool, storagePool, storageRegionSize, maxMemory) = memoryMode match {
case MemoryMode.ON_HEAP => (
onHeapExecutionMemoryPool,
onHeapStorageMemoryPool,
onHeapStorageRegionSize,
maxHeapMemory)
case MemoryMode.OFF_HEAP => (
offHeapExecutionMemoryPool,
offHeapStorageMemoryPool,
offHeapStorageMemory,
maxOffHeapMemory)
//尝试从存储内存借用内存
def maybeGrowExecutionPool(extraMemoryNeeded: Long): Unit = {
//计划从存储内存借用内存量
if (extraMemoryNeeded > 0) {
//计算最大可以借用内存量
//当存储内存池存在空余内存时,借用存储内存池空余内存;
//当存储内存池无空余内存,且存储内存池从执行内存池借用内存导致内存池容量大于最开始分片的内存容量时,归还从执行内存池借用的内存;
val memoryReclaimableFromStorage = math.max(
storagePool.memoryFree,
storagePool.poolSize - storageRegionSize)
//存在可以借用的内存
if (memoryReclaimableFromStorage > 0) {
//计算实际内存借用量:min(计划借用量,可以借用量);
//清除部分缓存,归还从执行内存池借用的内存
val spaceToReclaim = storagePool.freeSpaceToShrinkPool(
math.min(extraMemoryNeeded, memoryReclaimableFromStorage))
//对存储内存池进行缩容
storagePool.decrementPoolSize(spaceToReclaim)
//对执行内存池进行扩容
executionPool.incrementPoolSize(spaceToReclaim)
}
}
}
//计算执行内存池最大可用内存:最大可用内存 - min(存储内存池已用内存大小, 存储内存池大小)
//当存储内存池没有用完内存,剩余内存包含在执行内存池最大可用内存中,存在被占用可能
def computeMaxExecutionPoolSize(): Long = {
maxMemory - math.min(storagePool.memoryUsed, storageRegionSize)
}
//从执行内存池中分配内存资源
executionPool.acquireMemory(
numBytes, taskAttemptId, maybeGrowExecutionPool, () => computeMaxExecutionPoolSize)
}
}
3.3.1.执行内存池分配内存
说明:
- 在执行内存池中,通过HashMap类型的memoryForTask属性,缓存任务与已分配内存的对应关系;
- key为任务id,value为已分配给该任务的内存量;
- 将申请内存的任务维护到memoryForTask中;
- memoryForTask中没有当前任务的信息,添加进去,并唤醒等待的任务;
- 当执行内存不足时,尝试从存储内存中借用内存;
- 根据执行内存池最大可用内存、任务数计算单个任务可申请内存从范围:1/2n个内存池容量~1/n内存池最大可用内存量;
- 计算实际可分配内存;
- 不能超过执行内存池空闲内存,不能超过执行内存池容量的1/n,不小于0;
- 当内存不足时,等待;
- 内存不足:实际可分配内存 < 申请内存 && 总分配内存 < 任务执行需要最小内存;
- 当内存足够时,分片内存,将每个任务分配的内存信息维护在memoryForTask中;
private[memory] class ExecutionMemoryPool(
lock: Object,
memoryMode: MemoryMode
) extends MemoryPool(lock) with Logging {
//taskAttemptId -> 已分配内存;字节为单位
private val memoryForTask = new mutable.HashMap[Long, Long]()
private[memory] def acquireMemory(
numBytes: Long,
taskAttemptId: Long,
maybeGrowPool: Long => Unit = (additionalSpaceNeeded: Long) => Unit,
computeMaxPoolSize: () => Long = () => poolSize): Long = lock.synchronized {
assert(numBytes > 0, s"invalid number of bytes requested: $numBytes")
//将申请内存的任务添加到memoryForTask中
if (!memoryForTask.contains(taskAttemptId)) {
memoryForTask(taskAttemptId) = 0L
//唤醒等待的任务
lock.notifyAll()
}
while (true) {
//统计当前使用内存的任务
val numActiveTasks = memoryForTask.keys.size
//统计当前任务正在使用的内存
val curMem = memoryForTask(taskAttemptId)
//当执行内存不足时,尝试占用存储内存
maybeGrowPool(numBytes - memoryFree)
// 计算单个任务内存要求的范围:(1/2n ~ 1/n)个最大可用内存
val maxPoolSize = computeMaxPoolSize()
val maxMemoryPerTask = maxPoolSize / numActiveTasks
val minMemoryPerTask = poolSize / (2 * numActiveTasks)
// 最大可分配内存:0 <= X <= 1/n
val maxToGrant = math.min(numBytes, math.max(0, maxMemoryPerTask - curMem))
// 实际可分配内存:不能超过执行内存池空闲内存,不能超过最大可用内存的1/n
val toGrant = math.min(maxToGrant, memoryFree)
//内存不足:实际可分配内存 < 申请内存 && 总分配内存 < 任务执行需要最小内存;等待;
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
}
}
3.4.申请存储内存
说明:
- 根据申请的内存模式,获取存储内存池信息;
- 内存交易:计划申请内存量不能大于最大可用内存量;
- 最大可用内存量:
- 堆内存储内存池:堆内最大可用内存量 - 执行内存池已使用内存量;
- 堆外存储内存池:JVM外堆外最大可用内存 - JVM堆外执行内存池已用内存量;
- 最大可用内存量:
- 计划申请内存量大于存储内存池空余内存时,尝试从执行内存池借用内存;
- 计算需要从执行内存池借用的内存量:计划申请内存量与存储内存池空余内存差值;
- 借用内存不能超过执行内存池空余内存;
- 存储内存池分配内存;
- 当存储内存池空余内存不足时(扩容后),将内存池中部分缓存清除,用以满足分配内存要求;
private[spark] class UnifiedMemoryManager private[memory] (
conf: SparkConf,
val maxHeapMemory: Long,
onHeapStorageRegionSize: Long,
numCores: Int)
extends MemoryManager(
conf,
numCores,
onHeapStorageRegionSize,
maxHeapMemory - onHeapStorageRegionSize) {
override def acquireStorageMemory(
blockId: BlockId,//需要存储的数据块
numBytes: Long,//计划申请内存量
//用于存储的内存模式:堆内内存/堆外内存
memoryMode: MemoryMode): Boolean = synchronized {
//内存设置校验
assertInvariants()
assert(numBytes >= 0)
//获取存储内存池、存储内存池、最大可用内存
val (executionPool, storagePool, maxMemory) = memoryMode match {
case MemoryMode.ON_HEAP => (
onHeapExecutionMemoryPool,
onHeapStorageMemoryPool,
maxOnHeapStorageMemory)
case MemoryMode.OFF_HEAP => (
offHeapExecutionMemoryPool,
offHeapStorageMemoryPool,
maxOffHeapStorageMemory)
}
//计划申请内存量不能大于最大可用内存量
if (numBytes > maxMemory) {
// 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 ($maxMemory bytes)")
return false
}
//计划申请内存量大于存储内存池空余内存时,尝试从执行内存池借用内存
if (numBytes > storagePool.memoryFree) {
// 计算需要从执行内存池借用的内存量:计划申请内存量与存储内存池空余内存差值;
//借用内存不能超过执行内存池空余内存;
val memoryBorrowedFromExecution = Math.min(executionPool.memoryFree,
numBytes - storagePool.memoryFree)
//执行内存池索容
executionPool.decrementPoolSize(memoryBorrowedFromExecution)
//存储内存池扩容
storagePool.incrementPoolSize(memoryBorrowedFromExecution)
}
//存储内存池分配内存
storagePool.acquireMemory(blockId, numBytes)
}
}
3.4.1.存储内存池分配内存
说明:
- 内存校验:申请内存和空余内存不能小于0、内存池已用内存不能超过内存池容量;
- 判断是否需要从存储内存中清除部分缓存;
- 当扩容后内存池空余内存小于计划申请内存时,需要清除部分缓存;
- 判断扩容、清除部分缓存后的空余内存,是否满足计划申请内存量,达到则内存申请成功,否则失败;
- 内存申请成功,更新存储内存池内存使用量;
private[memory] class StorageMemoryPool(
lock: Object,
memoryMode: MemoryMode
) extends MemoryPool(lock) with Logging {
def acquireMemory(blockId: BlockId, numBytes: Long): Boolean = lock.synchronized {
//计算空余内存差值
val numBytesToFree = math.max(0, numBytes - memoryFree)
//分配内存
acquireMemory(blockId, numBytes, numBytesToFree)
}
def acquireMemory(
//申请内存的数据块
blockId: BlockId,
//申请内存
numBytesToAcquire: Long,
//空余内存
numBytesToFree: Long): Boolean = lock.synchronized {
//内存校验:申请内存和空余内存不能小于0、内存池已用内存不能超过内存池容量;
assert(numBytesToAcquire >= 0)
assert(numBytesToFree >= 0)
assert(memoryUsed <= poolSize)
//空余内存差值 > 0 :空余内存不足(扩容后)
if (numBytesToFree > 0) {
//从存储内存中清除部分缓存
memoryStore.evictBlocksToFreeSpace(Some(blockId), numBytesToFree, memoryMode)
}
// 内存充足,申请成功,否则申请失败
val enoughMemory = numBytesToAcquire <= memoryFree
if (enoughMemory) {
//生气成功,更新内存使用量
_memoryUsed += numBytesToAcquire
}
enoughMemory
}
}
4.tungsten内存管理
4.1.内存块
4.1.1.MemoryLocation-逻辑内存地址
说明:
- MemoryLocation可以通过内存地址(使用堆外分配)或与JVM对象的偏移量(堆内分配)跟踪;
public class MemoryLocation {
@Nullable
Object obj;//存储数据的对象:堆外内存时为null,堆内内存时为存储数据的对象
long offset;//存储数据的位置:堆外内存时为内存的绝对地址,堆内内存时为对象的偏移量
//适用堆内内存
public MemoryLocation(@Nullable Object obj, long offset) {
this.obj = obj;
this.offset = offset;
}
//适用堆外内存
public MemoryLocation() {
this((Object)null, 0L);
}
}
4.1.2.MemoryBlock-内存块
说明:
- 内存块
MemoryBlock
是逻辑内存地址MemoryLocation
的子类; - 定义了内存块构造器、内存块容量获取方式、获取一个以Long为元素的数组作为存储结构的内存块的方法、向内存块中填充数据的方法;
- 在以Long为元素的数组作为存储结构的内存块中,数组一个索引位占用1字节内存;
public class MemoryBlock extends MemoryLocation {
//表示未分配的页
public static final int NO_PAGE_NUMBER = -1;
//表示此页已经被TaskMemoryManager释放
public static final int FREED_IN_TMM_PAGE_NUMBER = -2;
//表示此页已经被MemoryAllocator释放
public static final int FREED_IN_ALLOCATOR_PAGE_NUMBER = -3;
//内存块容量
private final long length;
//由MemoryBlock分配的页号
public int pageNumber = -1;
//内存块构造器
public MemoryBlock(@Nullable Object obj, long offset, long length) {
super(obj, offset);
this.length = length;
}
//内存块容量
public long size() {
return this.length;
}
//构建一个以Long为元素的数组作为存储结构的内存块;数组一个索引位占用1字节内存
public static MemoryBlock fromLongArray(long[] array) {
return new MemoryBlock(array, (long)Platform.LONG_ARRAY_OFFSET, (long)array.length * 8L);
}
//填充数据
public void fill(byte value) {
Platform.setMemory(this.obj, this.offset, this.length, value);
}
}
4.2.MemoryAllocator-内存分配器
说明:
- 在内存分配器中维护非安全的内存分配器和堆内存分配器的实例化对象;
- 定义分配内存和释放内存的能力;
public interface MemoryAllocator {
boolean MEMORY_DEBUG_FILL_ENABLED = Boolean.parseBoolean(System.getProperty("spark.memory.debugFill", "false"));
byte MEMORY_DEBUG_FILL_CLEAN_VALUE = -91;
byte MEMORY_DEBUG_FILL_FREED_VALUE = 90;
//维护非安全的内存分配器
MemoryAllocator UNSAFE = new UnsafeMemoryAllocator();
//维护堆内存分配器
MemoryAllocator HEAP = new HeapMemoryAllocator();
//定义分配能力
MemoryBlock allocate(long var1) throws OutOfMemoryError;
//定义释放能力
void free(MemoryBlock var1);
}
4.2.1.UnsafeMemoryAllocator
说明:
- 定义自己分配内存和是否内存的逻辑;
- 分配内存:从系统内存中申请内存并返回内存地址,然后封装到内存块中返回;
- 是否内存:将指定地址的系统内存释放调,然后初始化内存块属性;
特别说明:
- 此分配器操作的是系统内存,不是JVM管理的内存;
public class UnsafeMemoryAllocator implements MemoryAllocator {
public UnsafeMemoryAllocator() {
}
//定义分配内存的逻辑
public MemoryBlock allocate(long size) throws OutOfMemoryError {
//在系统内存中申请size大小的内存,获取内存绝对地址
long address = Platform.allocateMemory(size);
//将申请的系统内存绝对地址和大小封装到内存块MemoryBlock中
MemoryBlock memory = new MemoryBlock((Object)null, address, size);
if (MemoryAllocator.MEMORY_DEBUG_FILL_ENABLED) {
memory.fill((byte)-91);
}
//返回内存块
return memory;
}
//定义释放内存的逻辑
public void free(MemoryBlock memory) {
//释放器校验与提醒
assert memory.obj == null : "baseObject not null; are you trying to use the off-heap allocator to free on-heap memory?";
assert memory.pageNumber != -3 : "page has already been freed";
assert memory.pageNumber == -1 || memory.pageNumber == -2 : "TMM-allocated pages must be freed via TMM.freePage(), not directly in allocator free()";
//
if (MemoryAllocator.MEMORY_DEBUG_FILL_ENABLED) {
memory.fill((byte)90);
}
//是否指定地址的系统内存
Platform.freeMemory(memory.offset);
//初始化内存块属性
memory.offset = 0L;
memory.pageNumber = -3;
}
}
4.2.2.HeapMemoryAllocator
说明:
- 以HashMap形式缓存不同大小的内存块数据池;
- key为内存块容量;
- value为以封装Long数组的WeakReference为元素的LinkedList链表;
- 数据池中内存块只能缓存1M以下的数据;
- 定义由分配内存块和释放内存块的逻辑;
- 分配内存块:
- 当数据量小于1M时,从数据中获取Long数组,根据获取的Long数组为存储结构,由数据量决定容量,构建内存块并返回;
- 当数据量不小于1M时,根据数据量构建Long数组,以Long数组为存储结构,构建内存块并返回;
- 是否内存块:
- 从内存块中获取Long数组类型的底层存储结构;
- 初始化内存块属性;
- 重置数据量对应的数据池:保证数据量对应的数据池存在,如果不存在,以数据量为容量构建一个新的数据池;
- 将从内存块获取的Long数组存储结构封装到WeakReference,然后添加到数据池中;
- 分配内存块:
特别说明:
- 此分配器操作的是JVM管理的堆内内存;
public class HeapMemoryAllocator implements MemoryAllocator {
//管理不同大小内存块的数据池
@GuardedBy("this")
private final Map<Long, LinkedList<WeakReference<long[]>>> bufferPoolsBySize = new HashMap();
//数据池中内存块容量上限:1M
private static final int POOLING_THRESHOLD_BYTES = 1048576;
public HeapMemoryAllocator() {
}
//判断数据是否超过数据池中内存块容量上限
private boolean shouldPool(long size) {
return size >= 1048576L;
}
//定义分配内存逻辑
public MemoryBlock allocate(long size) throws OutOfMemoryError {
//将内存块容量以8字节对齐:((size + 7) / 8);以字节为单位
int numWords = (int)((size + 7L) / 8L);
//内存块容量,以bit为单位
long alignedSize = (long)numWords * 8L;
assert alignedSize >= size;
//判断是否可以存放在数据池中
if (this.shouldPool(alignedSize)) {
synchronized(this) {
//获取alignedSize大小的数据池(链表)
LinkedList<WeakReference<long[]>> pool = (LinkedList)this.bufferPoolsBySize.get(alignedSize);
if (pool != null) {
while(!pool.isEmpty()) {
//从数据池中获取Long数组
WeakReference<long[]> arrayReference = (WeakReference)pool.pop();
long[] array = (long[])arrayReference.get();
if (array != null) {
assert (long)array.length * 8L >= size;
//根据从数据池获取的Long数组,构建内存块
MemoryBlock memory = new MemoryBlock(array, (long)Platform.LONG_ARRAY_OFFSET, size);
if (MemoryAllocator.MEMORY_DEBUG_FILL_ENABLED) {
memory.fill((byte)-91);
}
//返回内存块
return memory;
}
}
this.bufferPoolsBySize.remove(alignedSize);
}
}
}
//根据size构建以Long数组为存储结构的内存块
long[] array = new long[numWords];
MemoryBlock memory = new MemoryBlock(array, (long)Platform.LONG_ARRAY_OFFSET, size);
if (MemoryAllocator.MEMORY_DEBUG_FILL_ENABLED) {
memory.fill((byte)-91);
}
//返回内存块
return memory;
}
//定义释放内存逻辑
public void free(MemoryBlock memory) {
//释放器校验与提醒
assert memory.obj != null : "baseObject was null; are you trying to use the on-heap allocator to free off-heap memory?";
assert memory.pageNumber != -3 : "page has already been freed";
assert memory.pageNumber == -1 || memory.pageNumber == -2 : "TMM-allocated pages must first be freed via TMM.freePage(), not directly in allocator free()";
long size = memory.size();
if (MemoryAllocator.MEMORY_DEBUG_FILL_ENABLED) {
memory.fill((byte)90);
}
初始化内存块属性
memory.pageNumber = -3;
long[] array = (long[])((long[])memory.obj);
memory.setObjAndOffset((Object)null, 0L);
//重置size对应数据池
long alignedSize = (size + 7L) / 8L * 8L;
if (this.shouldPool(alignedSize)) {
synchronized(this) {
//保证size对应数据池存在
LinkedList<WeakReference<long[]>> pool = (LinkedList)this.bufferPoolsBySize.get(alignedSize);
if (pool == null) {
//如果不存在,构建一个数据池,添加到map中
pool = new LinkedList();
this.bufferPoolsBySize.put(alignedSize, pool);
}
//向数据中添加新的Long数组
pool.add(new WeakReference(array));
}
}
}
}
5.总结
内存模型总结:
- 通过
spark.executor.memory
指定executorContainer中JVM的堆内内存;- 300M预留内存,余下的为可用内存;
- 可用内存中60%为统一内存(最大可用内存),比例由
spark.memory.fraction
参数指定,默认0.6;余下40%为用户内存; - 统一内存中,50%为存储内存(Storage Memory),比例由
spark.memory.storageFraction
参数指定,默认0.5;余下50%为执行内存(Execution Memory);
- 通过
spark.memory.offHeap.enable/size
开启和指定JVM外的堆外内存,这些内存中50%为存储内存(Storage Memory),比例由spark.memory.storageFraction
参数指定,默认0.5;余下50%为执行内存(Execution Memory); - 存储内存和执行内存可以相互借用;
- 执行内存扩容:
- 执行内存不足,存储内存空余,执行内存池可以向存储内存池借用内存,借用内存不能超过存储内存池空余内存量;
- 执行内存不足,存储内存占用执行内存,可要求存储内存池归还占用的内存;
- 存储内存扩容:
- 存储内存不足,执行存储内存空余,存储内存池可以向执行内存池借用内存,借用内存不能超过执行内存池空余内存量;
- 存储内存不足,扩容后,还是达不到内存申请要求,将部分缓存清除,增加空域内存以达到申请要求;如果还是达不到要求,返回申请失败状态;
- 执行内存扩容:
- 在统一内存管理器中,通过存储内存池StorageMemoryPool和执行内存池ExecutionMemoryPool实现对内存的管理;
- 在同一内存管理器中,维护由内存分配器,该分配器提供内存分配和内存释放的功能;
tungsten内存分配器总结:
- 内存分配器有2种,分别管理JVM堆内内存和不属于JVM管理的堆外系统内存;
- UnsafeMemoryAllocator管理不属于JVM管理的堆外系统内存;
- HeapMemoryAllocator管理JVM堆内内存;
- UnsafeMemoryAllocator分配内存时,根据申请大小向系统内存申请,获取对应内存的绝对地址,将改地址封装在内存块MemoryBlock中;
- HeapMemoryAllocator分配器中,有一个以HashMap形式缓存不同大小的内存块的数据池;
- 当申请的内存小于1M时,从数据池中获取Long数组,然后封装到内存块MemoryBlock中返回;
- 当申请的内存不小于1M时,构建一个Long数组,然后封装到内存块MemoryBlock中返回;