前言:
遇到一个Linux系统 glibc内存分配导致的OOM问题,根源是内存回收出现问题,导致碎片太多,内存无法回收,系统认为内存不够用了。
涉及到以下知识点:
1、Linux中典型的64M内存区域问题
2、glibc内存分配器ptmalloc2的底层原理
3、glibc的内存分配原理(Arean、Chunk、bins等)
4、malloc_trim对内存回收的影响
1、问题描述
前段时间做POC,在测试的过程中发现一个问题,使用Flink集群Session模式反复跑批处理任务时,集群某些节点TaskManger总是突然挂掉。
查看挂掉节点的系统日志发现原因是:操作系统内存被耗尽,触发了系统OOM,导致Flink TaskManager进程被操作系统杀掉了,下图:
从图二可以看到,taskManager进程已经占了67%的内存40多G内存,继续跑任务还会继续增加
2、配置
2.1 测试环境及配置
测试使用的版本如下所示:
Flink 1.14.3
Icberg 0.13.1
Hive 3.1.2
Hadoop 3.3.1
jdk1.8.0_181
FLink Standalone配置
jobmanager.rpc.address: 127.127.127.127
jobmanager.rpc.port: 6123
env.java.opts: “-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/bigdata/dump.hprof”
jobmanager.memory.process.size: 2000m – flink JM进程总内存
jobstore.expiration-time: 36000 – 已完成任务保留时间,每个任务会消耗50M内存
taskmanager.memory.process.size: 22000m – Flink TM进程总内存
taskmanager.numberOfTaskSlots: 22
parallelism.default: 100
taskmanager.network.sort-shuffle.min-parallelism: 1 – 默认使用sort-shuffle,flink 1.15之后默认就是1
taskmanager.network.blocking-shuffle.compression.enabled: true – 是否启用压缩
taskmanager.memory.framework.off-heap.size: 1000m
taskmanager.memory.framework.off-heap.batch-shuffle.size: 512m
execution.checkpointing.interval: 60000
execution.checkpointing.unaligned: true – 启用未对齐的检查点,这将大大减少背压下的检查点时间
execution.checkpointing.mode: AT_LEAST_ONCE – 配置数据处理次数,至少一次可以减少背压,加快处理速度
io.tmp.dirs: /home/testdir – 临时文件存储位置,批处理和流处理,要注意磁盘空间是否够用
execution.checkpointing.checkpoints-after-tasks-finish.enabled: true – 打开已完成job不影响checkpoint
可以看到,在配置中,此TaskManager分配的内存为22G,实际上通过TOP看到的结果已经达到了40+G。
4、定位过程
4.1 查看内存占用情况
经过初步定位发现,在调用icebergStreamWriter的时候内存会猛涨一下,任务跑完之后,这块多出来的内存并不会回收,怀疑是申请了堆外内存做缓存,用完之后未释放
首先通过阿里的Arthas看一下内存情况
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.5.5
[INFO] Process 142388 already using port 3658
[INFO] Process 142388 already using port 8563
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
- [1]: 142388 org.apache.flink.runtime.taskexecutor.TaskManagerRunner
[2]: 2161 org.apache.ranger.authentication.UnixAuthenticationService
[3]: 142577 org.apache.flink.table.client.SqlClient
[4]: 170692 org.apache.hadoop.hdfs.server.datanode.DataNode
[5]: 170900 org.apache.hadoop.yarn.server.nodemanager.NodeManager
[6]: 73912 org.apache.flink.table.client.SqlClient
[7]: 141982 org.apache.flink.runtime.entrypoint.StandaloneSessionClusterEntrypoint
然后输入1,Enter
然后输入dashboard即可看到当前内存情况
可以发现进程的堆内存只有6.3G,非堆也很小,加起来不到6.5G,那另外30多G内存被谁消耗了,查看JVM内存分配,如下所示
- 堆(Heap):eden、metaspace、old 区域等
- 线程栈(Thread Stack):每个线程栈预留 1M 的线程栈大小
- 非堆(Non-heap):包括 code_cache、metaspace 等
- 堆外内存:unsafe.allocateMemory 和 DirectByteBuffer申请的堆外内存
- native (C/C++ 代码)申请的内存
- 还有 JVM 运行本身需要的内存,比如 GC 等。
初步怀疑这块内存应该是被native内存或者堆外内存消耗掉了
堆外内存可以使用NMT(Native Memory Tracking (NMT) )工具进行查看
NMT必须先通过VM启动参数中打开,不过要注意的是&