java内存分析工具-jmap/jstat/jvisual vm/mat -- 记一次Flink任务OOM问题的解决

8 篇文章 4 订阅

背景:最近用到flink做项目,程序在线上遇到了内存持续增长最后导致OOM的问题;还有一种情况是内存增长过高,在触发GC的时候产生超长停顿使taskmanager失去心跳而导致任务失败。

 

OOM问题比较难查,幸亏有团队的小伙伴一起帮着查询并解决。记录一下查询过程和中间用到的内存分析工具使用方法,以前很少用到这些工具,现在频繁使用,发觉其实也不是那么高深莫测。

堆dump

首先,对于内存超高的进程进行堆dump。命令如下:

jmap -dump:format=b,file=xxx pid 

jvisualvm查看堆文件

拿到dump文件后,尝试用jhat进行加载查看,但jhat是着实难用。后来用jvisualvm来查看,用一下命令指定其内存大小,不然有可能会出现内存过小没法加载dump文件的情况。

jvisualvm -J-Xms2048M -J-Xmx6144M

装入后可以看到内存中有哪些类的对象,对象的个数和大小。这个在网上也能找到更多的介绍,这里先不做说明了,因为visual vm也不好用,最好用的还是mat。

mat查看堆文件

mat是Memory Analyzer Tool的简称,本来是eclipse的一个插件,也可以单独下载这个工具使用。(下载方法:https://blog.csdn.net/hanchao5272/article/details/93379202

下载完后,修改MemoryAnalyzer.ini配置文件,将内存改成-Xmx6144m  (根据实际情况修改)

启动mat,加载堆文件,它会解析这个堆文件并生成很多的解析后结果,下次加载的时候就不会解析了,直接读取以前的解析结果,非常方便。加载后的首页结果如下:

本次主要用到了histogram,打开后如下图:

四列对应的关系为:

class name:类名

objects:该类有多少个实例

shallow heap:该类本身占用的内存大小,不包括它所引用的对象

retained heap:该类和它直接/间接引用的对象所占的内存总大小。

根据retained heap能看到程序中相关的数据是哪个占用内存比较大,然后定位到是flink中做join时两份数据的其中一份占用内存很高,同时追溯是谁引用了这个对象导致其不释放持续增长。

根据图中的关系发现是一个cogroup处的TaggedUnion直接引用了数据(这个没截图出来),然后再继续追溯发现有一些state操作,看来flink的窗口会把缓存的数据都当做状态数据,然后这里同时解释了之前的一个疑惑:为什么开启checkpoint的时候,会产生大io,因为这里的数据也是状态数据,也会进行checkpoint。

OK,发现了是在做cogroup的时候会缓存数据,但这个数据按道理应该在使用完后会释放才对(这个后来又在本机做了个实验,发现是可以GC回收的),为什么内存持续增长至OOM呢?(在线上也找到了某个进程,手动触发GC,发现内存也没有释放。触发GC的方法:jcmd pid GC.run)

然后想到,可能是在做cogroup的时候,有一个流的数据没来,导致单个流的数据持续积累,而且无法释放,最终把内存打满。

 

如何解决双流join时,一个流的数据不来使流程卡死的问题呢

1、使用window的trigger(不可行)

trigger是window的一个必备组件。

window有assigner、trigger、evictor三个组件,分别负责将数据分配到指定的窗口、触发窗口计算、窗口计算前过滤一些数据。

trigger可以根据任意自定义条件触发窗口,触发入口有两个:onElement()方法和onEventTime()方法(程序指定了使用事件时间),onElement是每个数据到来的时候都会触发这个方法,onEventTime是当watermark超过窗口最大时间的时候会触发。

为什么不可行:

(1)如果是onElement方法触发窗口计算,前提是数据已经被分配到这个窗口内了,如果不来数据怎么进行触发呢?

(2)如果是onEventTime方法触发窗口计算,这个前提是watermark已经做了更新。cogroup窗口的watermark取其两个数据源中watermark的最小值,如果一个流不来数据,那么窗口的watermark永远不能得到更新

(3)窗口触发计算不是目的,目的是窗口的清理,需要保证在窗口触发后能即时清理窗口。两个方法:触发窗口时同时清理数据,也就是在上面两个方法中返回TriggerResult.FIRE_AND_PURGE;实现clear()方法,这个方法需要保证和trigger的触发逻辑一直。

2、使用mock数据(可行)

构造一个source,定时产生一个fake数据,这个数据只带一个long类型的时间戳,这个时间戳要保证有一定的延迟。

这个流和另外两个流分别做connect,把数据传下去,一般来说,如果正常的流有数据,这个mock流产生的数据永远是延迟很重会被丢弃的;如果某个流出现了数据没来的情况,这个mock流的数据就不会被丢弃,可以用这个数据生成水印,从而触发窗口计算和窗口清除。

3、使用connect,代替cogroup

cogroup有问题的原因就是它会缓存两份数据,导致互相等待。

使用connect,直接糅合两个流,用两个流糅合后的流抽取时间戳和水印。然后在这个单流上做窗口,内部自己实现join逻辑。

4、将flink的状态后端改为rocksdb

当前flink的状态后端是filesystem,平常的状态数据会缓存在taskmanager里,做checkpoint的时候会写入文件系统;

改成rocksdb之后,状态数据会序列化写入rocksdb,做checkpoint的时候写入文件系统。

这样,就不会把内存撑爆了。

参考:https://www.jianshu.com/p/246b25cddc73

https://blog.csdn.net/lxhandlbb/article/details/90668040

启动后观察内存占用

有时候不需要进行堆dump来查问题,可能只需要看一下线上的进程运行情况,这时候需要配合使用多种工具。

查看gc情况

# 这个是查看各个代的内存大小、ygc和fgc的次数以及耗时
$ jstat -gc pid

# 这个和上面差不多,只不过是将各个区的占用情况用百分比表示,而不是绝对数量
jstat -gcutil pid

查看堆里的对象占用情况

# 查看内存中的所有对象信息,和上面的histogram展示的结果一样,有类名、对象个数、对象大小、对象能直接和间接引用到的对象的总大小
$ jmap -histo pid

# 这个是先触发一次full gc之后,再统计堆里面的内存占用情况
$ jmap -histo:live pid

查看堆里各个区的大小和已经使用的大小

jmap -heap pid

查看进程的启动信息,能看到jdk版本、jvm参数等信息

jinfo pid

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值