java.lang.OutOfMemoryError: Java heap space
首先要清楚的是,根据Spark的运行架构,我们知道Spark OOM Error主要发生在两个地方:Driver端和Executor端。
Driver端内存是通过配置“spark.driver.memory”来指定的,默认1g。注:在client模式下,此配置不能在应用程序中通过SparkConf设定,因为在SparkConf初始化之前,Driver JVM已经启动了。这时,可以通过在spark-submit命令行指定–driver-memory来设定,或者配置到Spark的配置文件(conf/spark_default.conf)中。
通常情况下,导致Driver端OOM的原因有以下两种:
比如,在大数据集(RDD/Dataset/DataFrame)上调用了collect()、collectAsList()、take()、takeAsList()等方法。这些方法会将要返回的数据从所有的Executor上发送到Driver端,如果返回的数据超过了Driver内存,就会引发OOM Error。
针对这种情况,我们有以下3中解决方法:
将RDD/Dataset进行广播时,会先将它们发送到Driver,然后由Driver端分发给每个Executor,如果我们的应用程序中广播了多个大变量,超出了Driver内存限制,就可能造成OOM Error。
针对这种情况,我们有以下2中解决方法:
Executor端内存是通过配置“spark.executor.memory”来指定的,默认1g。
Executor端OOM Error原因:
比如,在列式存储格式的表上执行select * from t返回不必要的列;没有提前使用过滤条件过滤掉不必要的数据。
所以,我们在查询的时候,要尽量将过滤条件前置;要尽量只读需要的列;分区表上只查询指定分区的数据等等。
每个Spark应用程序所需的内存都是不一样的,因此我们要结合所处理的数据的大小和应用监控(比如,Spark Web UI)来合理的为每个应用配置合适的Driver端和Executor端内存大小。
当我们的Spark任务运行在YARN上的时候,可能会因为container内存负载造成OOM Error,出现类似如下的报错:
ExecutorLostFailure (executor 5 exited caused by one of the running tasks) Reason: Container killed by YARN for exceeding memory limits. 5.0 GB of 5 GB physical memory used. Consider boosting spark.yarn.executor.memoryOverhead or disabling yarn.nodemanager.vmem-check-enabled because of YARN-4714.
解决方法:
如果Spark应用程序指定在内存中缓存了大的RDD/Dataset,或者是缓存了较多的RDD/Dataset,就有可能发生OOM Error:
val df: Dataset[Row] = spark.read.table("")
df.persist(StorageLevel.MEMORY_ONLY) //Dataset全部缓存到内存中
val rdd: RDD[Int] = ...
rdd.cache() //RDD全部缓存到内存中
Spark内存模型中,Execution内存和Storage内存的总大小为0.6*(heap space - 300MB),这总大小里面两者默认各占一半,我们应用中缓存的哪些数据集正是存储在Storage内存区域的。如果Spark应用程序中计算较少,那么我们可以通过spark.memory.storage适当调大Storage内存,或者通过配置spark.memory.fraction整体调大两者的总内存。
假如我们的Spark应用程序中要读取的表或文件数据量比较大,而我们没有配置合适的内存大小,并且分配了比较高的并发度和CPU核数,这些数据要在内存中计算,这就可能会造成Executor端OOM。
如果任务并行度较低,而某个任务又被分配了较多的数据,也会造成OOM。