再JVM虚拟机中,当创建的对象的数量很多时,Eden 和 Survior1 区域会很快的满溢,就需要进行频繁地 Minor GC,这样会导致有一些生命周期较短的对象迅速长到15岁并放入到老年代中,导致老年代中存放大量的短生命周期的对象(正常请况下,老年代应该存放的是数量比较少并且会长期使用的对象,比如数据库连接池),当老年代满溢后,会进行Full GC,Full GC是开启一个很消耗性能和时间的线程,而且不管 Minor GC 还是 Full GC 都会导致 JVM 的工作线程停止,因为 Scala 也是基于 JVM 的编程语言,所以运行 Spark 程序和运行 Java 程序在 JVM 中的内存分配情况是相同的。
当一个 Spark 的 task 执行时,可能会创建大数据量的对象,比如我们使用 foreach 来存数据的时候,有多少条数据就会创建多少个对象(自定义对象封装数据),有一千万条数据就会创建一千万个对象放到年轻代内存区中,导致频繁地 Minor GC 和 Full GC,如果 GC 仍然不能满足内存要求,就会报OOM错误。
总之,堆内存不足的造成的影响如下: (1) 频繁地 Minor GC (2) 老年代中大量的短生命周期的对象造成 Full GC (3) 有 GC 就会影响 Spark 的性能和运行速度
资源参数的调优,没有一个固定的值,需要根据自己的实际情况(包括Spark作业中的shuffle操作数量、RDD持久化操作数量以及spark web ui中显示的作业gc情况)来灵活的调优
可能造成该问题的原因: (1) 由于堆内内存不足导致 Executor 挂掉,从而 BlockManasger对象被回收
解决办法:
增加 Executor 的内存,调整--executor-memory(spark.executor.memory)
的值
(2) 由于堆外内存不足导致的Executor挂掉的话
Spark的shuffle部分使用了netty框架进行网络传输,但netty会申请堆外内存缓存(PooledByteBufAllocator ,AbstractByteBufAllocator);Shuffle时,每个Reduce都需要获取每个map对应的输出,当一个reduce需要获取的一个map数据比较大(比如1G),这时候就会申请一个1G的堆外内存,而堆外内存是有限制的,这时候就出现了堆外内存溢出
解决办法:提高堆外内存
--conf spark.yarn.executor.memoryOverhead=2048
这个配置项用于yarn集群,并且是在提交Application的时候指定的
--conf spark.executor.memoryOverhead=2048
这个配置项用于standalone集群,并且是在提交Application的时候指定的
(3) Executor没有挂掉,建立通信的时候发生了GC
解决办法:
--conf spark.core.connection.ack.wait.timeout=60
这个配置项必须是在提交Application的时候通过--conf来设置
(4) 拉取数据的时候遇到GC
解决办法:
增加重试次数和重试间隔时间
--conf spark.shuffle.io.maxRetries
--conf spark.shuffle.io.retryWait