You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache Spark Executor写入小数据时触发OutOfMemoryError问题排查

排查Spark Executor OutOfMemoryError问题求助

配置信息

  • Windows机器,Spark 3.1.3 local模式运行
  • JVM总内存设置为2g
  • Spark executor内存配置:spark.executor.memory=1g

场景复现

通过Spark SQL查询本地Parquet文件,测试超长记录处理能力,执行查询:

SELECT repeat('A', 50000000) AS test

将查询结果写入CSV文件时,JVM进程因java.lang.OutOfMemoryError: Java heap space崩溃。

疑问点

生成的数据约50MB,远小于1g的executor内存,但堆转储显示该字符数组存在大量副本,内存占用达800MB。而总数据量更大(如200MB)但无超长行的查询可正常运行。

  • Spark为单条记录生成大量副本是否正常?
  • 如何避免此类OOM?

异常栈信息

Thread 'Executor task launch worker for task 0.0 in stage 6.0 (TID 35)' with ID = 135
java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java:48)
java.util.Arrays.copyOf(Arrays.java:3332)
    com.univocity.parsers.common.input.ExpandingCharAppender.expand(ExpandingCharAppender.java:115)
    com.univocity.parsers.common.input.ExpandingCharAppender.expand(ExpandingCharAppender.java:123)
    com.univocity.parsers.common.input.ExpandingCharAppender.append(ExpandingCharAppender.java:207)
com.univocity.parsers.csv.CsvWriter.append(CsvWriter.java:405)
com.univocity.parsers.csv.CsvWriter.processRow(CsvWriter.java:228)
    com.univocity.parsers.common.AbstractWriter.submitRow(AbstractWriter.java:352)
    com.univocity.parsers.common.AbstractWriter.writeRow(AbstractWriter.java:830)
    org.apache.spark.sql.catalyst.csv.UnivocityGenerator.write(UnivocityGenerator.scala:94)
    org.apache.spark.sql.execution.datasources.csv.CsvOutputWriter.write(CsvOutputWriter.scala:46)
    org.apache.spark.sql.execution.datasources.SingleDirectoryDataWriter.write(FileFormatDataWriter.scala:140)

已查阅Spark内存管理资料,尝试缓存数据、仅持久化到磁盘,均未解决问题。


问题分析与解决方案

1. 单条记录产生大量副本是否正常?

不正常,这是Spark写入CSV依赖的Univocity Parsers库的特性导致的:

  • 写入超长字符串时,Univocity的ExpandingCharAppender采用倍增扩容策略:每次空间不足时,会将字符数组容量翻倍(比如从50MB扩容到100MB、200MB...),扩容过程中通过Arrays.copyOf生成原数组的副本,旧数组需等待GC回收。
  • 单条50MB的字符串在多次扩容后,内存中会同时存在多个不同容量的数组副本,叠加后占用内存远超原数据大小,这就是堆转储显示800MB占用的原因。
  • 短行数据因为扩容增量小,副本叠加的内存占比低,不会触发OOM,所以总数据量更大的短行查询能正常运行。

2. 避免此类OOM的方案

方案一:提前设置CSV Writer的字符缓冲区大小

Univocity允许配置初始缓冲区大小,避免频繁扩容和副本生成。在Spark中可通过参数指定:

// 写入CSV时添加配置
df.write
  .option("univocity.csv.writer.initial.buffer.size", "52428800") // 50MB,匹配生成的字符串大小
  .csv("path/to/output")

或全局配置:

spark.sql.csv.univocity.writer.initial.buffer.size=52428800

此配置会让Writer直接初始化足够大的缓冲区,避免多次扩容,从根源减少数组副本。

方案二:调整内存分配(临时应急)

Local模式下Spark会将部分内存分配给Driver等组件,可适当调高内存配置:

spark.executor.memory=1.5g
spark.driver.memory=512m

这只是临时缓解,无法解决副本产生的根本问题。

方案三:业务层面优化

如果业务允许,尽量拆分超长记录为多行;或改用Parquet、ORC等列式存储格式代替CSV,这类格式对大字段的处理更高效,不会出现CSV写入时的扩容副本问题。

方案四:升级Spark版本

Spark 3.2+对Univocity的CSV写入逻辑有优化,比如更智能的缓冲区初始化策略,能减少不必要的扩容操作,升级后可能直接解决该问题。


内容的提问来源于stack exchange,提问作者Yao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 15:01:33