YARN 任务经常报类似 Container is running beyond physical memory limits... Killing container 的异常,这是什么原因引起的?如何解决?
这个异常是 Hadoop YARN 中非常典型的 OOM(Out Of Memory)防范机制触发的错误。
简单来说:YARN NodeManager 发现某个 Container(容器)实际使用的物理内存超过了 YARN 分配给它的物理内存上限,为了保护所在机器不因内存耗尽而宕机,YARN 主动杀死了该 Container。
下面为您详细解析其产生的原因及对应的解决办法。
一、 核心原因解析:为什么会超限?
要理解这个错误,必须理清两个概念:YARN Container 内存限制 和 JVM 实际占用内存。
- YARN Container 内存限制:这是你通过参数告诉 YARN 的分配额度(例如分配 2GB)。YARN 会在操作系统层面监控这个进程树的总内存。
- JVM 实际占用内存:大多数 Hadoop/Spark 任务是 Java 进程。一个 Java 进程占用的总物理内存 = JVM 堆内存(Heap, 即
-Xmx配置的部分) + 堆外内存(Off-Heap,含元空间、线程栈、NIO 直接内存) + JVM 自身运行开销 + 任务可能派生的子进程(如 Python、Shell)。
最常见的踩坑点:
很多开发者把 YARN Container 内存和 JVM 最大堆内存 (-Xmx) 设置成了一样大。
例如:申请了 2GB 的 Container,并且设置了 -Xmx2g。当 JVM 跑起来后,除了这 2GB 堆内存,它还需要几百兆的堆外内存和开销。此时,进程实际占用的物理内存变成了 2.3GB,超过了 YARN 限制的 2GB,YARN 就会无情地将其 Kill 掉。
二、 解决办法
根据你使用的计算框架(MapReduce 或 Spark),解决参数会有所不同。核心解决思路都是:拉大 Container 内存与 JVM 堆内存之间的差值(留出足够的 Overhead 空间),或者直接增加总内存。
1. 如果是 MapReduce 任务
需要调整 mapred-site.xml 或在提交任务时指定参数。
经验法则:JVM 的 -Xmx 建议设置为 Container 内存的 75% ~ 80%。
- 针对 Map 阶段报错:
- 增加 Map Container 总内存:
mapreduce.map.memory.mb=2048(单位:MB,默认通常是1024) - 限制 Map JVM 堆内存:
mapreduce.map.java.opts=-Xmx1600m(留出400M给堆外)
- 增加 Map Container 总内存:
- 针对 Reduce 阶段报错:
- 增加 Reduce Container 总内存:
mapreduce.reduce.memory.mb=4096 - 限制 Reduce JVM 堆内存:
mapreduce.reduce.java.opts=-Xmx3200m
- 增加 Reduce Container 总内存:
2. 如果是 Spark on YARN 任务
Spark 有专门控制堆外内存/开销(Overhead)的参数。
计算公式:Container 总内存 = spark.executor.memory + spark.executor.memoryOverhead
- 方法A:增加堆外内存开销(推荐)
如果你的任务使用了大量 off-heap 内存(例如大对象序列化、或者使用了 PySpark 产生了 Python 进程),需要调大 overhead 参数:--conf spark.yarn.executor.memoryOverhead=1024(默认是 executorMemory 的 10%,最小 384M,往往不够用)。 - 方法B:整体增加 Executor 内存
--executor-memory 4g(配合上一步,YARN 实际分配的内存将是 4g + max(400M, 384M) = 4.4GB 左右)。
(注意:如果是 Driver 报错,对应的参数是 spark.driver.memory 和 spark.driver.memoryOverhead)
3. 修改 YARN 全局配置(治标/调试手段)
如果你确认机器内存非常充足,且不想频繁调整任务参数,可以在 YARN 集群的 yarn-site.xml 中关闭物理内存检查(注意:生产环境不推荐,可能会导致 NodeManager 所在机器真正 OOM):
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
<description>是否检查容器物理内存使用越界</description>
</property>
补充:如果报错是 virtual memory limits (虚拟内存超限),则关闭 yarn.nodemanager.vmem-check-enabled 即可,这个在 CentOS 7+ 系统上非常常见且建议关闭。
4. 从代码和业务逻辑层面优化(治本)
如果把内存调到很大(比如 8G、16G)还是报这个错,说明程序存在内存泄漏或逻辑极度不合理:
- 数据倾斜:某一个 Task 处理的数据量是其他 Task 的几十倍,导致该 Container 内存爆满。需要检查 group by / join 的 key 是否分布均匀。
- 集合过大:在代码中使用了巨大的
HashMap或List将海量数据一次性加载到内存中,而不是采用流式/迭代器处理。 - Spark 中的危险操作:对超大 RDD/DataFrame 直接使用了
.collect(),或者在mapPartitions中创建了大数组。 - 第三方库泄漏:某些 C/C++ 编写的底层库(如压缩/解压库)产生堆外内存泄漏。
总结排查步骤:
- 确认报错的是什么任务(MapReduce 还是 Spark)?是哪个阶段(Map/Reduce,Driver/Executor)?
- 检查当前提交任务的参数,查看
-Xmx是否等同于 Container 内存配置?如果是,拉开至少 20% 的差距。 - 调大总内存和 Overhead 测试任务能否跑通。
- 如果调大内存无效,检查是否存在数据倾斜或内存泄漏。