Spark 3.2读取JSON时Driver GC溢出,Spark 2.3无此问题
问题:Spark 3.2加载JSON DataFrame时Driver内存溢出,Spark 2.3无此问题
运行脚本
val files = fs.globStatus(path).filter{_.isDirectory == false} // 50个文件,每个约100MB val fileMap = scala.collection.mutable.Map.empty[String, DataFrame] files.par.foreach {f => val filename = f.getPath.getName.toString val df = spark.read.option("mode", "DROPMALFORMED").json(f.getPath.toString) fileMap.update(filename , df) }
问题现象
使用4GB Driver内存运行Spark 3.2时,出现GC Overhead错误;但相同参数下Spark 2.3可正常完成任务——Spark 2.3的JVM内存占用约2.3GB,Spark 3.2则超过4GB并崩溃。
两种场景下所有参数一致,JSON文件包含嵌套结构,约有800列。
疑问
- 代码未调用任何Action,仅存储DataFrame引用,为何Driver仍消耗大量内存?
- 两个Spark版本行为差异的原因是什么?
解答
1. 未触发Action仍消耗Driver内存的原因
Spark的DataFrame并非单纯的引用,它包含逻辑执行计划和完整的Schema元数据。读取JSON文件时,Spark必须先完成Schema推断:即便没有触发Action,这个推断过程会在Driver端执行,需要将包含800列的嵌套Schema完整加载到内存中。
另外,代码中使用了Scala的par.foreach并行集合,这会在Driver端启动多线程处理每个文件的Schema推断,每个线程生成的DataFrame元数据都会驻留在Driver堆内存中,大量元数据累积后就会占用可观的内存空间。
2. Spark 2.3与3.2的行为差异原因
核心差异来自Schema推断、元数据存储和内存管理的版本迭代:
- Schema推断逻辑增强:Spark 3.x对JSON Schema的推断更细致,比如精准区分数值类型(
long/int)、保留嵌套结构的更多细节,导致Schema元数据的体积比Spark 2.3显著增大。 - 元数据存储结构重构:Spark 3.x对Catalog和元数据管理做了重构,DataFrame的执行计划、元数据对象的内存占用比2.3版本更高,对于大列数的Schema,内存增幅会被放大。
- Driver端内存管理差异:Spark 3.x的Driver内存模型更严格,加上Scala并行集合的线程在Driver端运行时,内存分配效率不如2.3版本,同时3.x对对象引用的追踪更细致,GC压力剧增,最终触发GC Overhead错误。
临时解决方案
- 替换Scala并行集合:改用Spark分布式API处理文件读取(比如
spark.read.json(files.map(_.getPath.toString))),或者分批次处理,降低Driver端并发压力。 - 手动指定Schema:提前编写
StructType定义JSON结构,跳过自动Schema推断,大幅减少Driver内存占用。 - 调整Driver配置:增加Driver内存,或改用G1GC等更高效的垃圾回收器,缓解GC压力。
内容的提问来源于stack exchange,提问作者ASR
相关产品推荐
相关产品推荐

