spark 两种内存管理模式原理、源码以及conf参数调节
写在前面:
两种内存管理模式都将内存分为storge内存区域和execution内存区域,storge内存区域主要负责持久化RDD数据、和broadcast数据(广播数据),execution内存区域主要负责缓存在shuffle过程中中间数据。
一、静态内存管理
静态内存管理是saprk1.6版本之前所用的内存管理模式,spark以后的版本因为要兼容旧版本所以会留有相应的借口,在spark-submit时可用–conf spark.memory.useLegacyMode=true来控制,直接上源码
// 根据参数spark.memory.useLegacyMode确定使用哪种内存管理模型
val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)
val memoryManager: MemoryManager =
if (useLegacyMemoryManager) {// 如果还是采用之前的方式,则使用StaticMemoryManager内存管理模型,即静态内存管理模型
new StaticMemoryManager(conf, numUsableCores)
} else {// 否则,使用最新的UnifiedMemoryManager内存管理模型,即统一内存管理模型
UnifiedMemoryManager(conf, numUsableCores)
}
静态内存管理是通过StaticMemoryManager类来管理,以下是其构造方法,分别创建了storge内存区域和execute内存区域。
def this(conf: SparkConf, numCores: Int) {
// 调用最底层的构造方法
this(
conf,
// Execution区域(即运行区域,为shuffle使用)分配的可用内存总大小
StaticMemoryManager.getMaxExecutionMemory(conf),
// storage区域(即存储区域)分配的可用内存总大小
StaticMemoryManager.getMaxStorageMemory(conf),
numCores)
}
解释:静态内存管理中,内存通过conf参数分配了storge内存区域和execution内存区域。
/**
* Return the total amount of memory available for the storage region, in bytes.
* 返回为storage区域(即存储区域)分配的可用内存总大小,单位为bytes
*/
private def getMaxStorageMemory(conf: SparkConf): Long = {
// 系统可用最大内存,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存
val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)
// 取storage区域(即存储区域)在总内存中所占比重,由参数spark.storage.memoryFraction确定,默认为0.6
val memoryFraction = conf.getDouble("spark.storage.memoryFraction", 0.6)
// 取storage区域(即存储区域)在系统为其可分配最大内存的安全系数,主要为了防止OOM,取参数spark.storage.safetyFraction,默认为0.9
val safetyFraction = conf.getDouble("spark.storage.safetyFraction", 0.9)
// 返回storage区域(即存储区域)分配的可用内存总大小,计算公式:系统可用最大内存 * 在系统可用最大内存中所占比重 * 安全系数
(systemMaxMemory * memoryFraction * safetyFraction).toLong
}
/**
* Return the total amount of memory available for the execution region, in bytes.
* 返回为Execution区域(即运行区域,为shuffle使用)分配的可用内存总大小,单位为bytes
*/
private def getMaxExecutionMemory(conf: SparkConf): Long = {
// 系统可用最大内存,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存
val systemMaxMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)
// 取Execution区域(即运行区域,为shuffle使用)在总内存中所占比重,由参数spark.shuffle.memoryFraction确定,默认为0.2
val memoryFraction = conf.getDouble("spark.shuffle.memoryFraction", 0.2)
// 取Execution区域(即运行区域,为shuffle使用)在系统为其可分配最大内存的安全系数,主要为了防止OOM,取参数spark.shuffle.safetyFraction,默认为0.8
val safetyFraction = conf.getDouble("spark.shuffle.safetyFraction", 0.8)
// 返回为Execution区域(即运行区域,为shuffle使用)分配的可用内存总大小,计算公式:系统可用最大内存 * 在系统可用最大内存中所占比重 * 安全系数
(systemMaxMemory * memoryFraction * safetyFraction).toLong
}
解释:
1、getMaxStorageMemory方法,目的获取可用的storage内存大小
Conf可调参数:
(1)区域占总内存的比例,默认0.6
spark.storage.memoryFraction
(2)取storage区域(即存储区域)在系统为其可分配最大内存的安全系数,默认0.9,为了防止OOM异常发生
storagespark.storage.safetyFraction
(3)最后返回storage区域分配的可用内存总大小,计算公式:系统可用最大内存 * 在系统可用最大内存中所占比重 * 安全系数
2、getMaxExecutionMemory方法,目的获取可用的Execution内存大小
Conf可调参数:
(1)取Execution区域在总内存中所占比重,默认0.2
spark.shuffle.memoryFraction
(2)Execution区域在系统为其可分配最大内存的安全系数,默认0.8,为了防止OOM异常发生
spark.shuffle.safetyFraction
(3)最后返回Execution区域分配的可用内存总大小,计算公式:系统可用最大内存 * 在系统可用最大内存中所占比重 * 安全系数
storage区域默认占用内存比例为0.6,Execution区域默认占用内存比例为0.2,剩余的0.2为task运行程序所用内存。
二、统一内存管理
通过UnifiedMemoryManager类的apply()方法完成初始化的。还是直接上代码:
def apply(conf: SparkConf, numCores: Int): UnifiedMemoryManager = {
// 获得execution和storage区域共享的最大内存
val maxMemory = getMaxMemory(conf)
// 构造UnifiedMemoryManager对象,
new UnifiedMemoryManager(
conf,
maxMemory = maxMemory,
// storage区域内存大小初始为execution和storage区域共享的最大内存的spark.memory.storageFraction,默认为0.5,即一半
storageRegionSize =
(maxMemory * conf.getDouble("spark.memory.storageFraction", 0.5)).toLong,
numCores = numCores)
}
/**
* Return the total amount of memory shared between execution and storage, in bytes.
* 返回execution和storage区域共享的最大内存
*/
private def getMaxMemory(conf: SparkConf): Long = {
// 获取系统可用最大内存systemMemory,取参数spark.testing.memory,未配置的话取运行时环境中的最大内存
val systemMemory = conf.getLong("spark.testing.memory", Runtime.getRuntime.maxMemory)
// 获取预留内存reservedMemory,取参数spark.testing.reservedMemory,
// 未配置的话,根据参数spark.testing来确定默认值,参数spark.testing存在的话,默认为0,否则默认为300M
val reservedMemory = conf.getLong("spark.testing.reservedMemory",
if (conf.contains("spark.testing")) 0 else RESERVED_SYSTEM_MEMORY_BYTES)
// 取最小的系统内存minSystemMemory,为预留内存reservedMemory的1.5倍
val minSystemMemory = reservedMemory * 1.5
// 如果系统可用最大内存systemMemory小于最小的系统内存minSystemMemory,即预留内存reservedMemory的1.5倍的话,抛出异常
// 提醒用户调大JVM堆大小
if (systemMemory < minSystemMemory) {
throw new IllegalArgumentException(s"System memory $systemMemory must " +
s"be at least $minSystemMemory. Please use a larger heap size.")
}
// 计算可用内存usableMemory,即系统最大可用内存systemMemory减去预留内存reservedMemory
val usableMemory = systemMemory - reservedMemory
// 取可用内存所占比重,即参数spark.memory.fraction,默认为0.75
val memoryFraction = conf.getDouble("spark.memory.fraction", 0.75)
// 返回的execution和storage区域共享的最大内存为usableMemory * memoryFraction
(usableMemory * memoryFraction).toLong
}
解释:conf可调参数
storage区域内存大小占总内存的比例,默认0.5,execution区域为剩下的比例。
spark.memory.storageFraction
取可用内存所占比重默认0.75
spark.memory.fraction
总结:以下从网上截下两张图可直观的看出两种内存管理的区别和对应的可调参数