在内存调优中,通常有三点需要考虑:
默认情况下,对java的对象的访问时非常快的,但也因此比对象本身裸数据量消耗更多的内存(2~5倍不等)。这是由以下几个原因造成的:
Object Header
,该header中包含一些诸如指向该class的指针之类的信息。如果一个对象包含的数据量原本就很小(比如一个Int),那么该header就比数据本身还要大。wrapper
,该object不仅包含有链表header,同时还包含有下一个entry object的指针(通常8个Bytes)。java.lang.Integer
)该笔记会先对Spark中的内存管理进行一个概述,然后介绍一些特殊的用来优化应用程序的内存使用的策略。另外,还将介绍如何来决定用户对象的内存使用的经验以及如何通过修改用户数据的数据结构或者通过序列化格式来优化它们。最后还将介绍一些spark cache size和java GC方面的经验。
在Spark中内存的消耗通常在两个方面:execution
和storage
。Execution memory指的是在计算中用于做shuffle, sorts和aggregations的内存,而Storage memory指的是用来做cache和在cluster内传播数据的内存。在Spark中,execution和storage共享一个统一个内存空间(unified region - M)。当没有execution内存使用的时候,storage可以使用所有的空闲内存用来做cache,反之亦然。Exxcution在必要的时候有可能会释放storage内存,但仅仅会在所有的storage内存的使用率低于某一个阈值(R)之后才会这么做。而storage不会释放execution的内存。
这种设计是为了达到几种目的:
对不需要使用caching的应用程序,可以让其使用整个内存空间来进行execution,排除不必要的数据到磁盘的spill。
有两个选项能够对spark作业的内存管理进行配置(通常情况下使用期默认配置即可):
spark.memory.fraction
: default 0.6, 表示一定比例的JVM heap space(比如300MB)内存,这些内存即用来做execution和storage,剩下的内存用来提供给用户计算的消耗spark.memory.storageFraction
: default 0.5, 表示一定比例的M,所以默认的淘汰cache阈值为0.5 * 0.6 * JVM heap size。spark.memory.fraction
的配置需要设置的跟JVM的OLD或者“tunured generation”匹配。否则,当大部分的内存用来做caching和execution时,JVM的tenured generation有可能会满,从而导致JVM进行频繁的GC。这里可以参考Java GC sizing documentation
Tenured generation size是由JVM的NewRatio
参数来设置的,默认为2,表示tenured generation是new generation的2倍大小。所以,默认情况下,tenured generation占据了2/3或者67%左右的JVM heap size。
A value of 0.6 for spark.memory.fraction keeps storage and execution memory within the old generation with room to spare. If spark.memory.fraction is increased to, say, 0.8, then NewRatio may have to increase to 6 or more.
NewRatio is set as a JVM flag for executors, which means adding spark.executor.extraJavaOptions=-XX:NewRatio=x to a Spark job’s configuration.