一次oom我做了什么-(JVM,JMAP,MAT,等,大杂烩)

说明

这篇文章只是记录了因为一次oom我做了哪些思考和哪些动作,最终没有直接解决问题,但是过程收获满满,所以做了记录。

背景

项目描述:三节点,向其中一节点导入数据,通过蚂蚁金服开源的Jraft对另外两节点进行数据同步。
问题:发现其中一节点报出OOM,于是进行了下面这些操作。

oom

思考

看这个调用栈,看起来像jraft提交任务时抛出的,而且可以看到这里先是与其中一个节点28失联了(三个节点分别是28,29,30)。
失联为什么会oom呢,可能因为不断导入数据,数据无法同步,挤压过多导致oom。
但我是往失联的28上导入的,这个时候28与29心跳是正常的,数据也还在继续导入,只有30不通了,那30应该不会收到任务了,为什么还会oom呢?

想办法复现

因为项目是开发阶段,所以没有生产环境局促感,我直接删掉所有日志和数据文件,重启三个节点的服务,重新开始导入。
这次我用阿里开源的arthas来做监控,看看线程、内存和gc情况。
arthas这个的使用看官方文档就可以了。arthas官方文档

服务刚启动
启动
数据量700W的时候(这时候三个节点都是正常的)
700W
线程和内存方面都没什么大的异常,但是gc次数,是不是有点太多了?
gc次数,为啥会多来着?这里是年轻代的还是老年代的还是都算上?
我只知道项目用的是G1收集器,但是对于G1收集器没有很深的了解,这里先不做深究,后续有时间再研究。
这是两种收集算法的次数,目前只能盲猜数大的年轻代gc,数小的是full gc。
所以推测:年轻代gc次数多,说明年轻代放不下进来的数据,这样刷到老年代不会很慢,老年代持续增多,老年代gc也回收不了,最终超过了堆内存最大值,导致oom

堆内存中占地方的是什么

jmap -histo |sort -k 3 -g -r|less 堆中活着的对象排序
jmap -dump:live,format=b,file=heap.bin 打印堆日志
打出来的heap.bin有两种使用方式(我只知道两种)
一种是 直接jhat heap.bin,会启动一个服务,默认端口7000,在浏览器打开即可,拉到最下边的other,点击点击Heap Histogram,查看堆内存排名,其实和用jmap直接打在控制台是一样的,如图可以看到排名靠前的对象,当然jhat还有其他用法,我这里只是用来查看对象占用内存排序
Histogram
第二种是用MAT打开
我是看这篇博客入门使用的 MAT分析工具
这里记录一下我自己用到的地方

Leak Suspect视图中可以分析出疑似内存溢出的原因
在这里插入图片描述
点开其中一个问题详情
details
可以看到哪个线程在引用着大对象,导致不会被gc回收
可以看到哪个线程在引用着大对象,导致不会被gc回收
也可以通过Histogram视图
Histogram
查看占用内存较大对象,点击列名可进行排序,三列分别是对象数、占内存空间、回收后可释放的内存空间
查看占用内存较大对象,点击列名可进行排序,三列分别是对象数、占内存空间、回收后可释放的内存空间
选择截图这里,找到实例对象
选择截图这里,找到实例对象
同样可点击列名进行排序
排序
这里我们选择截图的菜单,来找到引用路径
路径
可以看到,同样是被LogManager这个类的线程引用着
1
当然,分析dump文件,应该在正常场景打出一份,在程序监控异常或oom时打出一份,做对比,那么如何做对比呢?

这里我们可以通过Navigation视图,去把当前的Histogram加入比较器中,打开另一个dump文件后同样加入
比较
加入后可以看到有两个Histogram,然后点击右上角的红色感叹号
感叹号
可以看到两次的对比数据,我这边一次是在运行过程中,一次是服务刚启动时打出的dump,可以明显看到,刚启动时哪些对象的个数/大小有增减
比较
还可以点击这里调整比较视图
比较
两种方式都指向一个地方,就是LogManager,但这个是Jraft的类,所以可能是我们的使用方案有问题?

调整JVM参数

既然暂时没办法通过代码级别的调优解决问题,那调整调整参数,合理分配一下内存试试
一些参数:
-Xms:最小堆内存 默认是物理内存的1/64 最小1M
-Xmx:最大堆内存 默认是物理内存的1/4 最小2M
-Xmn:年轻代大小 默认是物理内存的1/64
-Xss:单个线程栈 一般为默认512k
-XX:NewRatio 新生代内存容量与老生代内存容量的比例 默认为2
-XX:SurvivorRatio 默认为8
即分母为(1+1+8)Eden占新生代的8/10,From幸存区和To幸存区各占新生代的1/10
假如设置为6,则分母为(1+1+6)Eden占新生代的6/8,From幸存区和To幸存区各占新生代的1/8
官方推荐:
新生代占堆的3/8
幸存代占新生代的1/10

使用堆内存超过最小堆内存时会申请内存,变为最大堆内存;空余堆内存大于70%时,会减小导最小堆内存,调整成一样可避免堆自动扩展。

看到上面这句话,之前年轻代gc次数多就说得通了。
于是我先直接把最大和最小内存设置成一样大,并且修改为原来最大堆内存的二倍。

经过一顿分析和调整jvm参数后我用jstat查看了一下各代内存使用情况和gc次数,运行了一段时间后发现gc次数确实减少了,但是总体时间没有减少,因为堆内存大了,各代gc要收集的对象自然会变多,单次时间变长了,也就是所谓的时间换空间问题了

jstat -gc打印各代内存情况和gc情况
调整前
10
调整后
12
这样调整jvm参数可能会让程序运行的久一些,gc次数虽然少了,但是随着gc时间变长,依然会导致心跳不通,引发最终的oom。所以没有解决最初的问题。

收获

最终虽然没有解决实际工作上的问题,但是能够分析出一些可尝试的方向,也是没有白花时间,工作方案这里就不记录了。
更重要的是在整个过程中,把jvm参数,堆内存的分配,堆空间的使用情况,包括一些软件如arthas,MAT,jdk自带的这些jmap,jstat,jvisualvm(这次没有写到,下次用到再记录),都有了更深的理解和更熟练的使用。当然这些操作只是冰山一角,很多原理还不清楚,记录两篇个人觉得还不错的相关文章,第一个是阿里技术团队的文章,第二个是偶然看到的一个公众号写的。
美团技术团队公众号-Java中9种常见的CMS GC问题分析与解决
yes的练级攻略-一口气问了我18个JVM问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值