Spark本质上是基于内存的,当内存自己比较紧张,其性能也会受到影响。所以须要对内存使用进行优化,减小其消耗。经常使用的性能优化点包括:数据结构、序列器、缓存持久化与Checkpoint、JVM参数调优(由于本质上依赖GC)、并行度、共享数据与本地化存储、算子合理选择等方面。接下来咱们结合各个优化点,依次看看如何有效利用内存。apache
减小内存开销的重要手段之一就是优化数据结构。因为JAVA对象的特性,会隐含额外的内存开销,例如对象头与集合等。先看对象头,因为每一个对象都包含对象头(ObjectHeader),对象头分为两部分:MarkWord与ClassPointer(类型指针)。MarkWord存储了对象的hashCode、GC与锁信息;ClassPointer存储了指向类对象信息的指针。在32位JVM上对象头占用8字节,64位JVM则为16字节,两种类型的MarkWord和ClassPointer各占一半空间。所以某些数据存储时,可能内容自己会小于本身的对象头(如整型数据)。数组
图1:对象头示例缓存
集合类型内部会经过自定义对象来构造特定的数据结构,例如HashMap,内部会经过自定义类型Entry进行封装。一样这类对象除对象头还包含指针,也会额外占用内存。而对于存储基本数据类型的集合(例如int类型),内部会经过其对应的包装器进行封装(例如Integer),无形中也会扩大内存占用。优化Spark应用程序的内存,本质上是要优化自定义的算子函数中使用的局部变量,减小其对内存的占用。性能优化
优化的着手点能够考虑如下几个方面:数据结构
例如:List<Integer> list = new ArrayList<Integer>(),能够替换为:int[] array = new int[]。转换后的数组相比集合类型,一方面减小了元信息的存储开销;其次因为直接使用基本数据类型,下降了对象头的内存开销。app
因为多层次嵌套的对象中可能包含大量的小对象,所以能够对多层嵌套的对象结构进行拆解。例如:ide
public class Orders { private List<Items> items = newArrayList<Items>() }
能够替换为JSON:函数
{"orderId":1001,items:[{"itemId":1,"itemName":"cafe"},{"itemId":2, "itemName":"applewatch"}]}
一、设置RDD并行度(即partition数)。
二、调用RDD.cache()方法将RDD缓存到内存中。
三、观察Driver的日志能够找到相似于:
“INFO ... Added rdd_0_1 in memory on mbk.local:50311 (size: 717.5KB, free: 332.3 MB)”的信息,显示每一个partition占用的内存。
四、内存数量*partition数量,即RDD内存总占用量。性能
Spark算子一般会使用到外部的数据,当处理的数据比较大时,一样会对内存形成巨大的开销。所以引入了序列化技术,其实本质上就是对数据进行压缩,减小对内存的占用。Spark默认使用基于ObjectInputStream/ObjectOutputStream的原生序列化机制,其优势在于便捷性,但问题是其性能不高,所以某些场景下并不是最佳选择。优化
Kryo序列化机制——Spark支持使用Kryo类库来进行序列化,相对而言Kryo更快并且序列化后的数据占用更小。但缺点在于须要预先在Spark应用中对全部须要序列化的类型注册。若是不注册,Kryo必须保存类型的全限定名,反而会增大内存开销。
配置参与以启用Kryo序列化:
newSparkConf() .set("spark.serializer","org.apache.spark.serializer.KryoSerializer")
注册自定义类型以使用Kryo序列化:
SparkConf conf = newSparkConf().setMaster(...).setAppName(...) conf.registerKryoClasses(BigData.class) JavaSparkContext sc = newJavaSparkContext(conf)
此外值得注意的是,若是注册的序列化类型比较大,此时须要对Kryo缓存进行调整,由于可能内部缓存没法存放过大的对象。能够调用SparkConf.set("spark.kryoserializer.buffer.mb", x)方法进行调整(缓存默认大小为2M)。