Spark性能优化之JVM垃圾回收调优

目录

 

一:调优的背景

二:动作:(追踪+清理)

三:特点

四:监测垃圾回收

五:优化方式

1.代码层面

2.优化executor内存比例

3.JVM的空间分配

六:补充知识点


一:调优的背景


Java虚拟机会定期进行垃圾回收,或内存不够时主动触发,GC本身是一条线程,亦需要耗费内存
我们把Task运行的线程叫做工作线程,当GC运行时,会让工作线程直接停下来,让GC线程单独运行,那么直接影响了Spark应用程序的运行速度

二:动作:(追踪+清理)


此时就会追踪所有的java对象,并且在垃圾回收时,找到那些已经不在使用的对象,然后清理旧的对象

三:特点

 

垃圾回收的性能开销,是跟内存中的对象的数量,成正比的

 

四:监测垃圾回收


1.spark-submit脚本
增加一个配置即可,--conf "spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps"
打印出Java虚拟机的垃圾回收的相关信息,是输出到了worker上的日志中,而不是driver的日志中

2.SparkUI(4040端口)来观察每个stage的垃圾回收的情况


五:优化方式


1.代码层面


(1)使用更高效的数据结构,比如array和string      ————>  减小数据量大小
(2)持久化rdd时,使用序列化的持久化级别 ————> 把每个Partition都转换成一个字节数组,减小对象数量
(3)用Kryo序列化类库 ————> 减小数据量大小

2.优化executor内存比例


默认分配:
Spark使用每个executor 60%的内存空间来缓存RDD
40%的内存空间来存放,在task执行期间创建的对象

例子:
很有可能因为你的内存空间的不足,task创建的对象过大,那么一旦发现40%的内存空间不够用了,就会触发Java虚拟机的垃圾回收操作。因此在极端情况下,垃圾回收操作可能会被频繁触发。

本质取舍:
用增加读取RDD的时间(可能因内存不够而被磁盘化,或HDFS化),来降低GC回收的频率

方式:
用new SparkConf().set("spark.storage.memoryFraction", "0.5")

3.JVM的空间分配

1.图像:
 Spark性能优化之JVM垃圾回收调优
2.年轻代:放的是短时间存活的对象
Eden:主要存放空间
Survivor1,Survivor2:备用空间

年轻代中的Minor GC操作:
(1)创建的对象,首先放入Eden区域和Survivor1区域,如果Eden区域满了,那么就会触发一次 Minor GC
(2)Eden和Survivor1区域中需要存活的对象,会被移动到Survivor2区域中,GC将原来 Eden 和 Survivor1中的对象全部移除
(3)如果Survivor2区域中的内存空间小于待被放入的数据大小,则将多余部分放入老年代中
(4)Minor GC完成后,Survivor1与Survivor2进行角色互换,及Survivor2与Eden用于存放短时间存活的对象,而后依次循环

3.老年代:放的是长时间存活的对象
Full GC操作:
(1)在年轻代中,撑过了多次垃圾回收,都没有被回收掉,那么会被认为是长时间存活的,此时就会被移入老年代
(2)老年代的空间满了,那么就会触发Full GC,将老年代中没有使用的对象清除

4.存在情景
因为某个Survivor区域空间不够,在Minor GC时,就进入了老年代。从而造成短时间存活的对象,长期呆在老年代中占据了空间
导致Full GC时要回收大量的短时间存活的对象,亦或者使Full GC的频率变高,导致整体的 GC机制占用内存性能增多。

5.方式: 
(1)包括降低spark.storage.memoryFraction的比例,给年轻代更多的空间,来存放短时间存活的对象;
(2)给Eden区域分配更大的空间,使用-Xmn即可,通常建议给Eden区域,预计大小的4/3;
(3)如果使用的是HDFS文件,那么很好估计Eden区域大小,如果每个executor有4个task,然后每个hdfs压缩块解压缩后大小是3倍,此外每个hdfs块的大小是64M,那么Eden区域的预计大小就是:4 * 3 * 64MB,然后呢,再通过-Xmn参数,将Eden区域大小设置为4 * 3 * 64 * 4/3。


六:补充知识点


1.JVM中的内存溢出:
(1) 旧生代空间不足
旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
java.lang.OutOfMemoryError: Java heap space
为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
(2)Permanent Generation空间满
Permanent Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanent Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
java.lang.OutOfMemoryError: PermGen space

2.为什么给 年轻代的Eden区域分配的空间多于Survivor的空间? 
因为 Eden 区域满了,会触发 Minor GC操作,从而影响了整体的程序性能
结果:Eden区与Survivor区的大小比值。设置为4

3.Minor GC与 Full GC的比较?
(1) Minor GC 非常频繁,一般回收速度也比较快
  所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。 
(2)Full GC:清理整个堆空间—包括年轻代和永久代
 
4.英文词汇
Eden:伊甸园
Survivor:[sə'vaɪvə]  n. 幸存者;