概述
本編文章的內(nèi)容:
- 在executor端削祈,內(nèi)存的管理
- 在driver端尤莺,內(nèi)存的管理
1. executor內(nèi)存管理
1.1 堆內(nèi)存布局
## 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
}
如代碼所示曹铃,堆內(nèi)存被分為3塊妓局,一塊是預(yù)留內(nèi)存萌焰,一塊是額外使用內(nèi)存哺眯,一塊是spark用來管理的堆內(nèi)存,值如下:
systemMemory
的值為eden區(qū) + 一個(gè)surivior + old區(qū)
- 預(yù)留內(nèi)存
reservedMemory
為300M- 默認(rèn)情況下扒俯,額外使用內(nèi)存為剩余內(nèi)存的40%
- 默認(rèn)情況下奶卓,通過UnifiedMemoryManager管理的內(nèi)存為剩余內(nèi)存的60%
里面也包含了一層含義,在整個(gè)executor的運(yùn)行周期中撼玄,executor所有組件使用的內(nèi)存通常不會(huì)超過300M夺姑,由于用戶在rdd的transform或action中使用的內(nèi)存不可預(yù)知,spark.memory.fraction
應(yīng)該由用戶自己來控制掌猛,如果用戶在自己的程序中使用的內(nèi)存(如沒有使用Map盏浙、List等數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù))較少,可以相應(yīng)的調(diào)大spark.memory.fraction的值,這對(duì)shuffle和cache都有好處废膘;當(dāng)然這也不是必須的竹海,Reduce spark.memory.fraction default to avoid overrunning old gen in JVM default config描述了一種情況:當(dāng)spark.memory.fraction
設(shè)置過大時(shí),比如0.75殖卑,當(dāng)需要緩存大量數(shù)據(jù)到堆內(nèi)存中時(shí)站削,會(huì)導(dǎo)致長時(shí)間的full gc,原因如下:
- JVM的參數(shù)NewRatio默認(rèn)是2孵稽,說明老年代占用的堆大小的66%
- 在使用堆內(nèi)存緩存數(shù)據(jù)時(shí)许起,最大的內(nèi)存占比為
spark.memory.fraction
,即75%- 此時(shí)數(shù)據(jù)不能完全放入到old區(qū)而導(dǎo)致full gc菩鲜,影響程序性能
1.2 內(nèi)存管理
Spark通過MemoryManager
來管理內(nèi)存园细,它有兩個(gè)實(shí)現(xiàn)類:StaticMemoryManager
、UnifiedMemoryManager
接校,前者基本沒有用了猛频,后者有如下特點(diǎn):
- 內(nèi)存分為2部分,一部分為存儲(chǔ)內(nèi)存蛛勉,另一個(gè)部分為執(zhí)行內(nèi)存
- 默認(rèn)情況下鹿寻,存儲(chǔ)內(nèi)存占整個(gè)管理內(nèi)存的50%,由
spark.memory.storageFraction
決定诽凌,其余的為執(zhí)行內(nèi)存- 存儲(chǔ)內(nèi)存與執(zhí)行內(nèi)存在對(duì)方有空閑空間時(shí)毡熏,可以互借;但是侣诵,執(zhí)行內(nèi)存不會(huì)主動(dòng)釋放痢法,如果執(zhí)行內(nèi)存借了存儲(chǔ)內(nèi)存,此時(shí)存儲(chǔ)內(nèi)存不夠需要執(zhí)行內(nèi)存歸還,被借的內(nèi)存由于執(zhí)行內(nèi)存占用不會(huì)歸還杜顺。而當(dāng)執(zhí)行內(nèi)存不夠而存儲(chǔ)內(nèi)存借用的執(zhí)行內(nèi)存會(huì)被歸還
UnifiedMemoryManager
除了使用上述的堆內(nèi)存外财搁,還可以使用堆外內(nèi)存,由spark.memory.offHeap.enabled
開啟躬络,由spark.memory.offHeap.size
確定堆外內(nèi)存的大小尖奔,堆外內(nèi)存也分為存儲(chǔ)內(nèi)存和執(zhí)行內(nèi)存,存儲(chǔ)內(nèi)存占比也是由spark.memory.storageFraction
決定
1.3 執(zhí)行內(nèi)存的使用
如果需要使用
MemoryManager
管理的執(zhí)行內(nèi)存穷当,都需要繼承MemoryConsumer
越锈,在學(xué)習(xí)代碼的過程中,發(fā)現(xiàn)堆內(nèi)存與堆外內(nèi)存并沒有混合使用膘滨,即堆外內(nèi)存與堆內(nèi)存不會(huì)互借(如果有甘凭,請(qǐng)讀者不吝指出),可能實(shí)現(xiàn)比較麻煩火邓,我總結(jié)了上圖的內(nèi)存使用場(chǎng)景丹弱。其實(shí)上圖也比較好理解德撬,因?yàn)?code>ExternalSorter、ExternalAppendOnlyMap
里面的數(shù)據(jù)都是反序列化的躲胳,所以需要使用堆內(nèi)存蜓洪;而ShuffleExternalSorter
的數(shù)據(jù)都是序列化的,可在堆內(nèi)也可在堆外坯苹,可能因?yàn)槿≈祵?shí)現(xiàn)起來麻煩所以堆外內(nèi)存與堆內(nèi)存不會(huì)互借隆檀。每個(gè)任務(wù)能使用的最大執(zhí)行內(nèi)存為該executor執(zhí)行內(nèi)存 / 該executor正在執(zhí)行的任務(wù)數(shù),最小執(zhí)行內(nèi)存為該executor執(zhí)行內(nèi)存 / 2 * 該executor正在執(zhí)行的任務(wù)數(shù)粹湃,當(dāng)任務(wù)需要的內(nèi)存大于最小執(zhí)行內(nèi)存恐仑,但是實(shí)際能分配給它的內(nèi)存又小于最小執(zhí)行內(nèi)存時(shí),則會(huì)阻塞等待为鳄,直到其它的任務(wù)釋放完內(nèi)存后喚醒它裳仆。
1.4 存儲(chǔ)內(nèi)存的使用
在spark-core中,只有兩種情形會(huì)是使用存儲(chǔ)內(nèi)存孤钦,一種是RDD需要緩存歧斟,二是廣播變量,其消耗的內(nèi)存類別如上圖偏形。
1.5 未被MemoryManager管理的內(nèi)存使用
- 各個(gè)組件用來緩存的數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)静袖,如
Map
,List
俊扭;這是走JVM管理的- 用戶在自己的代碼中使用的數(shù)據(jù)結(jié)構(gòu)
- 在
NettyBlockTransferService
向外傳輸數(shù)據(jù)和接收數(shù)據(jù)勾徽,當(dāng)數(shù)據(jù)小于spark.storage.memoryMapThreshold(默認(rèn)2MB)
,分配傳輸?shù)?code>ByteBuffer為HeapByteBuffer
,會(huì)分配在堆上统扳;但是最終netty會(huì)使用堆外內(nèi)存(默認(rèn))將數(shù)據(jù)傳輸出去