一、原理概述
①理论
Spark是用Scala开发的。Spark的Scala代码调用了很多java api。Scala也是运行在JVM中的,所以Spark也是运行在JVM中的。
②JVM可能会产生什么样的问题?
内存不足——RDD的缓存、task运行定义的算子函数,可能会创建很多对象,占用大量的内存。处理不当,可能导致JVM出问题。
③堆内存
- 作用:存放项目中创建的对象。
- 划分:新生代(young generation,Eden区域+survivor区域1+survivor区域2,比例8:1:1),老年代(old generation)
④GC(垃圾回收)
每次创建出来的对象,都会放到Eden区域和survivor区域1中,另外一个survivor区域空闲。
由于spark作业产生的对象过多,当Eden区域和survivor区域放满之后,就会触发minor gc(初代回收)。把不再使用的对象从内存中清理出去,给后面对象的创建腾出空间。
清理掉了不再使用的对象之后,那些存活下来(还需要继续使用)的对象,放入之前空闲的survivor区域2中。当survivor区域2满了放不下,JVM会通过担保机制机制将多余的对象直接放到老年代中。
如果JVM内存不够大,可能导致频繁的新生代内存溢出,频繁的minor gc。频繁的minor gc会导致短时间内,有些存活下来的对象,经过多次垃圾回收都没有回收掉,导致这种生命周期短(不一定会长期使用)的对象,年龄过大,进入老年代。
老年代中存在过多的短生命周期的、本该在新生代中可能马上要被回收的对象,导致内存不足,频繁内存满溢,频繁进行full gc(老年代回收)。full gc会回收老年代中的对象。由于老年代中的对象数量少,满溢进行的full gc频率本应该很少,所以回收算法很简单,但是耗费性能和时间。——full gc很耗时间。
full gc/minor gc,无论快慢,都会导致JVM工作线程停止工作,spark作业会暂停,等待垃圾回收完成之后继续工作。
⑤总结---内存不足导致的问题
- 频繁minor gc,导致spark频繁停止工作
- 老年代囤积大量活跃对象(短生命周期的对象),导致频繁full gc,full gc时间很长,短则数十秒,长则数十分钟,甚至数小时,导致spark长时间停止作业
- 严重影响spark作业的性能和运行的速度
二、降低cache操作的内存占比
spark中,堆内存被划分为两块,一块专门用来给RDD的cache、persist操作进行数据缓存使用;一块用来给spark算子函数的运行使用,存放函数自己创建的对象。
默认情况下,给RDD cache操作的内存占比是0.6,但是可能cache真正需要使用的内存不需要这么多,而存储spark算子函数创建对象需要大量的内存,这个时候可以调节这个参数,比如0.5、0.3、0.2等等。
用yarn运行spark的时候,通过yarn页面可以查看spark作业的运行统计,一层一层点进去,可以看到每个stage的运行情况,包括每个task的运行时间、gc时间等。根据情况,如果gc频繁,时间太长,可以适当地调整这个比例,反复测试,直到调整到一个合适的比例。
在spark的代码中设置示例:SparkConf.set("spark.storage.memoryFraction","0.6")