1. 先把"进程内存"这件事讲明白
从 1.10(TaskManager)/1.11(JobManager)开始,Flink 用更严格的方式控制 JVM 进程内存。核心概念就两个:
- Total Flink Memory:Flink 自己可控、可分配的内存(堆 + off-heap 等)
- Total Process Memory:整个 JVM 进程的总内存(Flink 内存 + JVM 自身开销)
最简单的配置方式是二选一:
-
配 Total Flink Memory
- TaskManager:
taskmanager.memory.flink.size - JobManager:
jobmanager.memory.flink.size
- TaskManager:
-
配 Total Process Memory
- TaskManager:
taskmanager.memory.process.size - JobManager:
jobmanager.memory.process.size
- TaskManager:
其余组件(network、overhead、metaspace 等)会根据默认值或你额外的微调自动推导。
重要规则(很多人踩坑就在这):
-
非本地执行时,三种方式必须选一种,否则 Flink 启动直接失败
- 方式1:配
*.memory.flink.size - 方式2:配
*.memory.process.size - 方式3:配关键内部组件(TM 是 task heap + managed;JM 是 heap)
- 方式1:配
-
不建议同时显式配置 total process + total flink,容易出现冲突导致部署失败
2. JVM 参数到底怎么被 Flink "算"出来的?
Flink 启动进程时,会根据你配置/推导出的组件大小,自动拼出 JVM 关键参数(这是排查 OOM 的关键线索):
-
-Xmx/-Xms- TaskManager:Framework + Task Heap(给堆的总量)
- JobManager:JVM Heap
-
-XX:MaxDirectMemorySize- TaskManager:Framework off-heap + Task off-heap + Network(Direct 的上限)
- JobManager:默认不一定加,只有开启
jobmanager.memory.enable-jvm-direct-memory-limit才会加
-
-XX:MaxMetaspaceSize- 两者都是 JVM metaspace
所以你遇到 Direct buffer memory,第一反应不该是"把 Xmx 拉大",而是看 MaxDirectMemorySize 里到底谁不够。
3. 两类"按比例但有上下限"的组件:最容易引发启动失败
Flink 有些组件不是固定值,而是"按比例算",但又被 min/max 夹住:
- JVM Overhead:可以是 total process 的 fraction,且必须在 min/max 范围
- Network Memory:可以是 total flink 的 fraction(仅 TaskManager),且必须在 min/max 范围
举个很真实的例子:
- total process = 1000MB
- overhead fraction=0.1 → 100MB
- overhead min=128MB → 最终 overhead 会被抬到 128MB
再提醒一句:这些组件最终值不落在 min/max 范围内,Flink 会启动失败,不是"将就跑"。
4. TaskManager 内存:真正影响稳定性的"组件级拆解"
TaskManager 跑用户算子,内存模型比 JobManager 更复杂。核心组件与配置项如下(生产最常动的我会重点标出来):
-
Task Heap(重点):
taskmanager.memory.task.heap.size保障用户代码/算子在 JVM 堆里可用空间
-
Managed Memory(重点):
taskmanager.memory.managed.size或taskmanager.memory.managed.fraction
用于 RocksDB、排序、Hash、缓存、Python UDF 等,属于 off-heap native
-
Network Memory(重点):
taskmanager.memory.network.fractiontaskmanager.memory.network.min/taskmanager.memory.network.max
任务间数据交换 buffer(Direct),反压与吞吐常常卡在它
-
Task Off-heap:
taskmanager.memory.task.off-heap.size你的用户代码如果分配 unmanaged off-heap(direct/native),要算进来
-
Framework Heap / Framework Off-heap(高级,没证据别动):
taskmanager.memory.framework.heap.sizetaskmanager.memory.framework.off-heap.size
-
JVM Metaspace:
taskmanager.memory.jvm-metaspace.size -
JVM Overhead:
taskmanager.memory.jvm-overhead.fraction/min/max
4.1 Managed Memory 的"消费者权重":RocksDB + Python 一起跑必看
如果你的作业同时有多种 managed memory 消费者,可以用:
taskmanager.memory.managed.consumer-weights
消费者类型:
OPERATORSTATE_BACKEND(RocksDB)PYTHON
例如 RocksDB + Python UDF,希望 7:3 分:
properties
taskmanager.memory.managed.consumer-weights: STATE_BACKEND:70,PYTHON:30
注意:如果你手动覆盖 weights,把某个实际需要的类型漏掉,会导致内存分配失败。默认是全包含。
4.2 本地 IDE 运行:别被"看似生效"的配置骗了
本地起一个 Java 进程跑 Flink(不建集群)时,很多配置会被忽略。真正相关的默认值只有:
- task heap:默认"无限"
- task off-heap:默认"无限"
- managed:默认 128MB
- network:默认 64MB(min/max)
此时真实 JVM 堆大小由你启动参数决定(-Xmx/-Xms),不是 Flink 帮你控。
5. 场景化 Memory Tuning:按用例选配置,不要一套模板走天下
5.1 Standalone 部署(物理机/非容器):优先配 total Flink memory
建议配置:
taskmanager.memory.flink.sizejobmanager.memory.flink.size
必要时再调 metaspace
原因:Standalone 下 total process memory 的"JVM overhead"不由 Flink 或环境精确控制,更多取决于机器的物理资源与 JVM 行为。
5.2 容器化部署(Kubernetes/YARN):优先配 total process memory
建议配置:
taskmanager.memory.process.sizejobmanager.memory.process.size
原因:process size 直接对应"你请求的容器内存大小"。
关键警告(容器被杀的根因之一):
- 如果 Flink 或用户代码分配了"未受控"的 off-heap/native 内存,超过容器大小,环境会直接杀掉容器,作业失败
所以容器里最怕的是"unmanaged native"失控,而不是单纯 heap 不够。
一个容易忽略的点:
- 你如果只配 total flink memory,Flink 会把 JVM 组件隐式加上,推导出 total process memory,然后按推导值去申请容器
5.3 Netty4(RPC + Shuffle buffer):资源紧张时可微调 allocator
Flink RPC 也走 Netty4 之后,byte buffer 行为变化明显。你可以通过 JVM property 配置 allocator 类型,这会同时影响 RPC 与 Shuffle:
可选:
pooledunpooledadaptive
配置示例(写在 conf/config.yaml):
yaml
env:
java:
opts:
jobmanager: -Dorg.apache.flink.shaded.netty4.io.netty.allocator.type=unpooled
taskmanager: -Dorg.apache.flink.shaded.netty4.io.netty.allocator.type=unpooled
额外一个性能向选项(JDK 11+):
yaml
env:
java:
opts:
jobmanager: -Dorg.apache.flink.shaded.netty4.io.netty.tryReflectionSetAccessible=true
taskmanager: -Dorg.apache.flink.shaded.netty4.io.netty.tryReflectionSetAccessible=true
它通常能减轻 GC 压力、提升性能。
5.4 State Backend:HashMap vs RocksDB,内存策略完全不同
HashMapStateBackend(或无状态作业):
- 建议把 managed memory 设为 0
目的:把尽可能多的内存留给 heap,让用户代码更舒服
RocksDB(EmbeddedRocksDBStateBackend):
- RocksDB 用 native memory
- 默认情况下 RocksDB 会把 native 分配限制在 managed memory 大小内
结论:你必须给足 managed memory,否则 RocksDB 状态一大就容易抖或 OOM
危险动作:
- 如果你关闭 RocksDB 默认内存控制,在容器环境中 RocksDB 可能分配超过容器限制,直接 OOMKilled
5.5 Batch 作业:managed memory 是性能关键,不够就会 spill
Batch 算子会尽量使用 managed memory 来跑排序/Hash/Join 等,优势是避免大量 Java 对象反序列化,性能更稳。Flink 会在 managed 不足时"优雅落盘 spill",而不是直接 OOM,但代价是磁盘 IO 上升、延迟变大。
6. 生产排错速查:你看到某类错误,第一刀该砍哪里?
-
Java heap space- 优先:增加 task heap 或整体 heap(别只盯 process size)
- 同时检查:对象分配热点、序列化、GC
-
Direct buffer memory- 优先:检查 MaxDirectMemorySize 的预算是否足够
- 常调:network min/max/fraction、task off-heap(用户 direct/native)、框架 off-heap(高并行/依赖)
-
容器
OOMKilled/ memory exceeded- 优先:用
*.memory.process.size对齐容器 request/limit - 排查:是否有 unmanaged native(RocksDB 失控、JNI、第三方库)超出容器
- 优先:用
7. 一句话配置策略(拿去当团队默认规范)
- Standalone:配
*.memory.flink.size - K8s/YARN:配
*.memory.process.size - HashMap/无状态:managed=0,把内存留给 heap
- RocksDB:managed 给足,别轻易关 RocksDB 内存控制
- 反压/吞吐卡住:优先看 network memory
- Direct OOM:别只加 Xmx,先看 direct 预算是谁吃掉的
- 不要同时显式配 total flink + total process,冲突会让你"起不来就很尴尬"