在SortShuffleWriter和UnsafeShuffleWriter都提到了执行内存的申请,为了阐明内存的申请过程,咱们先看看内存管理器。
内存管理器
Executor启动的时候,就会创立一个内存管理器MemoryManager(默认UnifiedMemoryManager),MemoryManager对存储体系和内存计算所应用的内存进行治理。内存又分为堆外内存和堆内存,所以MemoryManager就有4个内存池,别离为堆内存的存储内存池onHeapStorageMemoryPool、堆外内存的存储内存池offHeapStorageMemoryPool、堆内存的执行计算内存池onHeapExecutionMemoryPool、堆外内存的执行计算内存池offHeapExecutionMemoryPool,每个内存池都记录着内存池的大小和已应用的大小。
下图把堆内和堆外的执行计算的内存和存储的内存并在一起,是因为他们之间并没有一个显著的界线,比如说堆内执行计算的内存不够用了,就会向堆内存储的内存“借”内存,堆内存储的内存也会向堆内执行计算的内存“借”内存。堆外执行计算的内存和存储的内存也是能够互相“借”内存。
内存消费者和工作内存管理器
Task的资源分配中曾经晓得,Driver会把task封装成TaskDescription,交给Executor去执行。Executor会把TaskDescription封装成TaskRunner并放入线程池中,如果须要内部排序的话,最初会用到UnsafeShuffleWriter的ShuffleExternalSorter或者SortShuffleWriter的ExternalSorter。这两个内部排序器其实都是内存消费者MemoryConsumer。
内存消费者仅仅定义了内存消费者的标准,实际上对内存的申请、开释是由工作内存管理器TaskMemoryManager来治理的。工作内存管理器实际上依赖于MemoryManager提供的内存治理能力,所以Executor同时有多个TaskRunner在执行的时候,就会有多个内存消费者,每个内存消费者都会通过工作内存管理器对内存管理器申请、开释内存。
申请内存
内存消费者为了排序性能的进步,会把RDD的数据集事后寄存在内存中,这个内存是须要向内存管理器申请的。
咱们假设内存模式是堆内存,那申请的内存就是堆内存的执行计算内存池,内存池的大小_poolSize为100。每个执行内存池中都保护着一个map,叫做memoryForTask,key是工作内存管理器的身份标识taskAttemptId,value是工作内存管理器已申请的内存大小。
此时,taskAttemptId为1的工作内存管理器去堆内存的执行计算内存池申请10内存,他发现memoryForTask里并没有taskAttemptId为1的key,于是就把key为1,value为0赋值给memoryForTask。
每个工作所能申请的内存范畴是poolSize/2N
到maxPoolSize/N
之间,这个N是memoryForTask中key的数量,poolSize是以后内存池的大小,maxPoolSize是以后内存池可应用的最大大小,这个值是包含堆内存的存储内存池的局部甚至全副内存大小,也就是说,如果执行计算内存池不够用了,是能够从存储内存池借的。当然存储内存池也能够向执行计算内存池借内存。
目前执行计算内存池的内存可能满足taskAttemptId为1所须要的内存,所以间接给分配内存,并更新memoryForTask中taskAttemptId为1的值为10。
taskAttemptId为2的工作内存管理器去堆内存的执行计算内存池申请50内存,流程同上。此时线程池中,可用内存就剩下100-10-10-50=30了。
taskAttemptId为3的工作内存管理器去堆内存的执行计算内存池申请20内存,流程同上。此时线程池中,可用内存就剩下100-10-10-50-20=10了。
taskAttemptId为3的工作内存管理器去堆内存的执行计算内存池持续申请20内存,此时内存曾经不够用了,执行计算内存池就会去存储内存池要回借出去的内存(如果有的话),并且向存储内存池借了10内存,taskAttemptId为3的值就变成了20+20=40。此时这4个工作内存管理器申请的内存曾经超过了堆内存的执行计算内存池。
taskAttemptId为4的工作内存管理器去堆内存的执行计算内存池申请20内存,然而执行计算内存池和存储内存池借来的内存曾经不满足20,或者比要求的内存最小值还小,以后线程处于期待状态。
内存开释
taskAttemptId为2的工作内存管理器此时开释了40内存,此时对应的value就变成了50-40=10,而后唤醒所有阻塞的线程,比方下面的taskAttemptId为4对应的线程。
taskAttemptId为4对应的线程被唤醒后,发现能够申请20,于是更新对应的值。
taskAttemptId为2的工作内存管理器此时持续开释了10内存,此时对应的value就变成了10-10=0,因为小于等于0了,所以这个key就删除掉,并且再唤醒所有阻塞的线程。