Spark应用Executor堆内存过高咨询:执行/存储内存占比低
Spark Executor内存问题解答
1. 对Executor堆内内存划分的理解是否正确?
你的理解基本准确,但需要补充Spark 3.2.1中堆内内存划分的细节:
- 预留内存(Reserved Memory):默认300MB,用于存储Spark核心内部对象,不可手动调整;当配置的
spark.executor.memory小于1.5GB时,Spark会自动调整预留内存占比,但你的Executor配置为8GB,所以固定为300MB。 - 用户内存(User Memory):默认占总堆内存(扣除预留内存)的20%,完全由用户代码支配,Spark框架不会主动占用这部分内存,用于存储自定义数据结构、UDF、应用元数据等。
- Spark内存(Spark Memory):剩余的80%总堆内存(扣除预留内存),分为执行内存和存储内存,两者动态共享(默认比例50:50),执行内存用于shuffle、join、排序等计算操作,存储内存用于缓存RDD、广播变量,当一方内存不足时可向另一方借用空闲内存。
2. 堆内JVM内存峰值过高的原因
即使执行与存储内存占比低,堆内内存峰值仍高达6.27GiB,可能由以下原因导致:
- 用户内存隐性占用:
- Delta Lake读取时的元数据、分区信息、事务日志对象可能占用大量用户内存;
- 任务执行过程中生成的临时对象(如未及时回收的中间数据结构)堆积在用户内存。
- 序列化效率低下:默认使用Java序列化,对象序列化后内存开销大,导致堆内临时对象占用过多内存。
- GC回收延迟:JVM垃圾回收不及时,老年代内存堆积大量无用对象,无法及时释放,推高内存峰值。
- 任务并行度与内存叠加:Executor配置5核,同时运行5个任务时,每个任务的临时对象内存叠加,导致整体内存峰值上升。
- 数据倾斜:某个Executor分配到远多于其他节点的任务,单任务处理大量数据时内存占用激增。
3. 内存优化方案(针对执行/存储内存占比低的场景)
排查与监控
- 查看Spark UI的Executor页面,确认
User Memory列的占用情况,定位内存消耗来源; - 开启GC日志(设置
spark.executor.extraJavaOptions=-XX:+PrintGCDetails -XX:+PrintGCTimeStamps),分析内存回收效率; - 检查Spark UI的Stages页面,排查是否存在数据倾斜的任务。
内存配置调整
- 若用户内存无明显占用,可调整内存比例:增大
spark.memory.fraction(默认0.8)至0.9,减少用户内存占比,将更多内存分配给Spark执行/存储内存; - 调整Executor核心数:将5核降至4核,减少同时运行的任务数,降低内存叠加压力。
序列化优化
- 改用Kryo序列化:设置
spark.serializer=org.apache.spark.serializer.KryoSerializer,并注册自定义类(通过spark.kryo.registrator),大幅降低对象序列化后的内存开销。
GC优化
- 启用G1GC垃圾收集器:设置
spark.executor.extraJavaOptions=-XX:+UseG1GC -XX:MaxGCPauseMillis=200,减少GC停顿,提升内存回收效率; - 调整新生代比例:设置
-XX:NewRatio=1,增大新生代内存占比,让临时对象在新生代快速回收,减少老年代内存堆积。
Delta Lake优化
- 执行
OPTIMIZE命令整理Delta表,减少读取时的文件数量,降低元数据处理的内存开销; - 启用Delta缓存:设置
spark.databricks.delta.cache.enabled=true,优化数据读取的内存复用。
任务并行度调整
- 调整
spark.sql.shuffle.partitions:若数据量不大,将默认200调至与Executor核心数匹配的数值(如4*3=12),减少小任务数量,降低内存碎片化。
内容的提问来源于stack exchange,提问作者Jonathan
相关产品推荐
相关产品推荐

