静态内存管理
在 Spark 最初采用的静态内存管理机制下,存储内存
、执行内存
和其他内存
的大小在 Spark 应用程序运行期间均为固定
的,但用户可以应用程序启动前进行配置,堆内内存的分配如图 2 所示:
可以看到,可用的堆内内存的大小需要按照下面的方式计算:
可用堆内内存空间:
可用的存储内存 = systemMaxMemory * spark.storage.memoryFraction * spark.storage.safetyFraction
可用的执行内存 = systemMaxMemory * spark.shuffle.memoryFraction * spark.shuffle.safetyFraction
其中 systemMaxMemory
取决于当前 JVM 堆内内存
的大小,最后可用的执行内存或者存储内存要在此基础上与各自的 memoryFraction
参数和 safetyFraction
参数相乘得出。
上述计算公式中的两个 safetyFraction
参数,其意义在于在逻辑上预留出 1-safetyFraction
这么一块保险区域,降低因实际内存超出当前预设范围而导致 OOM 的风险(上文提到,对于非序列化对象的内存采样估算会产生误差)。值得注意的是,这个预留的保险区域仅仅是一种逻辑上的规划,在具体使用时 Spark
并没有区别对待,和"其它内存"一样交给了 JVM
去管理。
堆外的空间分配较为简单,只有存储内存和执行内存,如图 3 所示。可用的执行内存和存储内存占用的空间大小直接由参数 spark.memory.storageFraction
决定,由于堆外内存占用的空间可以被精确计算,所以无需再设定保险区域。
静态内存管理机制
实现起来较为简单,但如果用户不熟悉 Spark 的存储机制,或没有根据具体的数据规模和计算任务或做相应的配置,很容易造成"一半海水,一半火焰"的局面,即存储内存和执行内存中的一方剩余大量的空间,而另一方却早早被占满,不得不淘汰或移出旧的内容以存储新的内容。由于新的内存管理机制的出现,这种方式目前已经很少有开发者使用,出于兼容旧版本的应用程序的目的,Spark
仍然保留了它的实现。
统一内存管理
Spark 1.6
之后引入的统一内存管理机制,与静态内存管理的区别在于存储内存和执行内存共享同一块空间,可以动态占用对方的空闲区域,如图4和图5
其中最重要的优化在于动态占用机制
,其规则如下:
- 设定基本的存储内存和执行内存区域(
spark.storage.storageFraction
参数),该设定确定了双方各自拥有的空间的范围 - 双方的空间都不足时,则存储到硬盘;若己方空间不足而对方空余时,可借用对方的空间;(存储空间不足是指不足以放下一个完整的 Block)
- 执行内存的空间被对方占用后,可让对方将占用的部分转存到硬盘,然后"归还"借用的空间
- 存储内存的空间被对方占用后,无法让对方"归还",因为需要考虑
Shuffle
过程中的很多因素,实现起来较为复杂
凭借统一内存管理机制,Spark
在一定程度上提高了堆内和堆外内存资源的利用率
,降低了开发者维护 Spark
内存的难度,但并不意味着开发者可以高枕无忧。譬如,所以如果存储内存的空间太大或者说缓存的数据过多,反而会导致频繁的全量垃圾回收,降低任务执行时的性能,因为缓存的 RDD 数据通常都是长期驻留内存的 。所以要想充分发挥 Spark
的性能,需要开发者进一步了解存储内存和执行内存各自的管理方式和实现原理。
总结
- 注意2.0+版本
storage
是占可用的内存的50%.而不是全部内存的50%