之前的项目部署在物理机上手动部署,现在需要将其虚拟化在虚拟机上部署,结果发现频繁出现OOM如图:
1. 虚拟机部署的环境采用套餐为s, 单核cpu, 容器内存大小为2G, 启动脚本中jvm内存限制为 1G*10/8,启动JVM参数为
work 932 1 4 13:34 pts/1 00:13:16 /home/work/emcjdk11/runtime/bin/java -Xms816m -Xmx816m -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=127.0.0.1:8139,suspend=n -server -Xss320K -XX:+UseG1GC -XX:MetaspaceSize=1024m -XX:+PrintGC -verbose:gc -Xloggc:log/gc.log -Djava.io.tmpdir=log/temp -Dserver.port=8138 -Dmanagement.server.port= -Dformula.launcher.framework-path=/home/work/emclauncher/runtime/lib/formula-framework.jar -Dformula.launcher.application-path=lib/compass-server-app.jar -jar /home/work/emclauncher/runtime/lib/formula-launcher.jar
2. 怀疑是线下环境资源设置过低,进行容器升级,所以采用大套餐。8核 容器内存大小为20G. 实际jvm限制 16G, 启动JVM参数为
work 26525 1 7 16:15 pts/0 00:12:47 /home/work/emcjdk11/runtime/bin/java -Xms16384m -Xmx16384m -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=11394,suspend=n -server -Xss320K -XX:+UseG1GC -XX:MetaspaceSize=256m -XX:+PrintGC -verbose:gc -Xloggc:log/gc.log -Djava.io.tmpdir=log/temp -Dserver.port=11393 -Dmanagement.server.port= -Dformula.launcher.framework-path=/home/work/emclauncher/runtime/lib/formula-framework.jar -Dformula.launcher.application-path=lib/compass-server-app.jar -jar /home/work/emclauncher/runtime/lib/formula-launcher.jar
3. 经过多人并发请求,不一会就又OOM。 gc日志如下:
4. 这个肯定不是配置能解决的问题,应该是系统内存泄漏或者其他原因,深入排查,先看原来的物理机上的使用情况 (原先线上5台物理机),发现线上单机java进程占用内存为17G. 明显不符合业务场景。单日请求不超过2W. 单机请求不超过5k
5. 深入排查,jmap -dump:live,format=b,file=heap.bin 26525 将当前的dump文件 导出,并通过mat进行分析。 (由于文件过大,目前导出正常请求时候的dump文件)。 由于线程很少(40+),基本排除多线程引起,初步判断大对象的问题,先看看大对象有哪些。
不看不知道,一看吓一跳,居然有几百M的大对象,我们需要一步步进行处理
5.1 com.mysql.jdbc.JDBC42ResultSet @ 0x40da072f8 占用233M, 单次查询(单个对象24Byte, 一共居然返回了182W+的数据加载在内存)
SELECT meg_trade_name_1st_2b as meg_trade_id_1st_2b, meg_trade_name_1st_2b, meg_trade_name_2nd_2b as meg_trade_id_2nd_2b, meg_trade_name_2nd_2b FROM xxxxx WHERE (ucid = xxx AND post = '销售总经理')
直接查询果然出问题了,返回了几十万个数据,怎么会出现这种事情呢? 继续跟进,发现业务逻辑出问题了 meg_trade_name_1st_2b 写错成meg_trade_id_1st_2b
5.2 java.lang.Thread java线程的引用堆166MB
发现一个大的list对象。继续看里面的信息,发现还是第一个sql导致,在 mysql将数据序列化成 list
5.3 com.mysql.jdbc.JDBC42ResultSet @ 0x41ef36bd8 占用内存64.9M
同样的方法排查,发现sql为select post as post, post as post_name from xxx where ucid = xxx 返回60W数据
进一步排查发现逻辑问题:未去重
频繁的占用大内存,会导致频繁的gc,影响性能,同时也浪费资源。后端服务上jarvis后 内存限制未16G,所有的数据接口必须控制结果的数据量,理论上加载在内存中的数据量不能超过1W.
mat 内存分析工具下载 Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation
dump 文件下载 jmap -dump:live,format=b,file=heap.bin 进程号
线上:线程分析,可利用jarvis 的 应用诊断功能 JSTACK分析