【OOM】记录一次生产上的OutOfMemory解决过程
一.项目架构
SpringCloud Dalston.SR1 + SpringBoot 1.5.9 + Mysql +Redis + RabbitMQ
所有的业务模块的应用服务都部署在同一个服务器,且单实例部署,服务器配置4核32G,
二. 原因分析:
自己所负责的data模块这两天OOM较多,导致服务重启;
data服务主要业务是报表相关,数仓对接的业务以及多个外部数据相关的小程序的后台,与数据库的交互比较多,业务逻辑相对其他模块较为简单,
第一次:2月25日OOM情况:
由于Redis反序列化失败导致的OOM

第二次:2月26日的OOM情况:
由于GC无法回收对象导致

第一次发生OOM时,觉得可能就是由于Redis序列化器和反序列化器不一致,原有的JVM参数仅设置时-Xmx:512m -Xms:512m, 老年代:年轻代=2:1 ,老年代大概分配有300M内存
时候排查问题时,发现Redis的使用都是用自己用RedisTemplate封装的工具类,按道理说不会出现什么问题,并未过多关注;
第二次发生OOM时,与第一次相距的时间仅为1天,当时就觉得问题不对了,
1.首先使用jmap -histo:live pid 查看 服务内存活的对象,发现 [C 类型的数组和ConcurrentHashMap对象都存活较多;
检查代码后发现并未有显示的使用该两类类型,怀疑时String字符串过多导致的;
2.其次使用JDK自带的分析工具:jmap -dump:format=b,file=文件名 [pid] 导出OOM时的dump日志;
导出时间非常慢,且占用线上系统的CPU,导致CPU达到100%
3.使用jstat -gc pid /jstat -gcutil pid 查看gc的状况
发现gc和fgc的都非常多,特别是fgc已经达到1000多次;

初步解决方案:(2月26日)
最后仍然是重启服务,-添加参数Xmx1024m -Xms:1024m
然后添加JVM参数(使用jinfo -flag可以在生产环境上直接添加)
jinfo -flag +HeapDumpBeforeFullGC pid
jinfo -flag +HeapDumpAfterFullGC pid
jinfo -flag +HeapDumpOnOutOfMemoryError pid
jinfo -flag +HeapDumpPath=/home/xxx/xxx pid 添加dump日志的目录(需要提前建好)
jinfo -flag -XX:+PrintGCDetails pid 开启gc日志
jinfo -flag -XX:+PrintGCDateStamps -Xloggc:/xxx/xxx 设置gc日志的目录
修改完成后第二天根据fgc产生的dump日志,加载到jvisualVM里面之后发现也是[C占用内存较多
下午 2点左右,监控线上服务时发现Old老年代的内存占用为300M,总大小为700M,经过一次FGC之后占用70M,这就比较正常了;
重点来了:
在2月26日添加完成JVM参数后,第二天同样的接口,FGC之前终于拿到了dump文件,大小是1.4G,接下来就是分析dump文件了,这里我选择了两个工具:
MAT与Jvisualvm
在使用体验来说JDK自带的Jvisualvm真的很垃圾,文件打开都要半个小时,果断放弃,转而使用MAT
导入dump文件以后如图

这里主要是看Leak Suspects:其他的几个指标在此也说明一下:
1. Histogram可以列出内存中的对象,对象的个数以及大小。
2. Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。
3.Top consumers通过图形列出最大的object。
4.Leak Suspects通过MA自动分析泄漏的原因。
打开Leak Suspects后可以看到线程堆栈如图

再继续找,找到是否有我们的业务代码。找到如图

这里其实已经定位到具体的业务代码了,但是MAT的强大之处就是可以定位究竟是什么大对象,
如图,这里已经可以看到了6W多个HashMap被Object[]引用,这里是内存占用的主要原因
OK,接下来可以取看业务代码了


业务代码如下,只展示关键代码,这个接口是报表数据导出的接口,查询mysql后使用HashMap去接收结果集,
Object[]用于是用于写入报表工具类的入参;
查看服务器日志后,发现这条SQL有6W多条数据,而且在一分钟之内有人操作导出了两次,导致数据封装到HashMap里面,发生FGC

三 最终解决方案:
1.加大堆内存 原来由512扩大到1024M;
2.HashMap改为JavaBean对象去封装结果集,因为HashMap底层是数组,还有其他的引用成员变量,更加有频繁的扩容,
查资料后发现HashMap在数据量的情况下内存占用比Java对象要大;
3.导出接口添加限流注解,防止在短时间内多次请求;
以下是限流代码:使用Guava的限流组件实现,当然也可以基于Redis的实现,或者自己实现一套
4.由于EasyExcel内存占用少,可以将poi换成阿里的EasyExcel,实现多条数据分页导出;
/**
* @author: Gabriel
* @date: 2020/2/18 12:03
* @description 自定义接口限流注解
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimitAnno { /** 每秒放入令牌桶中的token */
double limitNum() default 20;
} /**
* @author: Gabriel
* @date: 2020/2/18 12:07
* @description
*/
@Slf4j
@Aspect
@Component
public class RateLimitAspect { /**
* 用来存放不同接口的RateLimiter(key为接口名称,value为RateLimiter)
*/
private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>(); private RateLimiter rateLimiter; @Autowired
private static ObjectMapper objectMapper = new ObjectMapper(); @Autowired
private HttpServletResponse httpServletResponse; @Pointcut("@annotation(com.gabriel.stage.annotation.RateLimitAnno)")
public void rateLimit() {
} /**
* 环绕通知
*
* @param joinPoint
* @return
* @throws Exception
*/
@Around("rateLimit()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object obj = null;
//获取拦截的方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Object target = joinPoint.getTarget();
//获取注解信息
Method method = target.getClass().getMethod(signature.getName(), signature.getParameterTypes());
RateLimitAnno annotation = method.getAnnotation(RateLimitAnno.class);
double limitNum = annotation.limitNum();
//获取方法名
String functionName = signature.getName();
//获取类名
String className = signature.getDeclaringTypeName();
signature.getDeclaringTypeName();
if (StringUtils.isNotBlank(className)) {
className = StringUtils.substringAfterLast(className, ".");
}
//拼接类名和方法名,保证key唯一
String joinName = StringUtils.join(functionName, className); //获取rateLimiter
if (map.containsKey(joinName)) {
rateLimiter = map.get(joinName);
} else {
map.put(joinName, RateLimiter.create(limitNum));
rateLimiter = map.get(joinName);
} if (rateLimiter.tryAcquire()) {
obj = joinPoint.proceed();
} else {
System.err.println("接口限流,请求降级。。。。。。。。。。。。。。。。。");
throw new BusinessException(ResultCode.SERVER_ERROR);
}
return obj;
}
【OOM】记录一次生产上的OutOfMemory解决过程的更多相关文章
- [转]线上GC故障解决过程记录
排查了三四个小时,终于解决了这个GC问题,记录解决过程于此,希望对大家有所帮助.本文假定读者已具备基本的GC常识和JVM调优知识,关于JVM调优工具使用可以查看我在同一分类下的另一篇文章: http: ...
- 一次线上GC故障解决过程记录
排查了三四个小时,终于解决了这个GC问题,记录解决过程于此,希望对大家有所帮助.本文假定读者已具备基本的GC常识和JVM调优知识,关于JVM调优工具使用可以查看我在同一分类下的另一篇文章: http: ...
- 【Feign/Ribbon】记录一次生产上的SpringCloudFeign的重试问题
在上周在的微供有数项目中(数据产品),需要对接企业微信中第三方应用,在使用Feign的去调用微服务的用户模块用微信的code获取access_token以及用户工厂信息时出现Feign重试超时报错的情 ...
- 记录一次服务器CPU 100%的解决过程
昨天客户反馈业务系统很慢,而且偶尔报错. 查看nginx日志: [root@s2 nginx]# tail log/error.log 2017/03/14 12:54:46 [error] 1704 ...
- 记录CentOS 7.4 上安装MySQL&MariaDB&Redis&Mongodb
记录CentOS 7.4 上安装MySQL&MariaDB&Redis&Mongodb 前段时间我个人Google服务器意外不能用,并且我犯了一件很低级的错误,直接在gcp讲服 ...
- PermGen space OOM 记录
前些天线上除出了个OOM问题,今天闲下来记录下: OOM的提示信息是-PermGen space,说明问题出在方法区,方法区存的是什么东西?:类的加载信息.常量.静态变量. 按照方法区的定义:类加载的 ...
- 生产上数据库大量的latch free 导致的CPU资源耗尽的问题的解决
中午的时候,我们生产上的某个数据库,cpu一直居高不下 通过例如以下的sql语句,我们查看当时数据库的等待,争用的情况: select s.SID, s.SERIAL#, 'kill -9 ' || ...
- Linux(2)---记录一次线上服务 CPU 100%的排查过程
Linux(2)---记录一次线上服务 CPU 100%的排查过程 当时产生CPU飙升接近100%的原因是因为项目中的websocket时时断开又重连导致CPU飙升接近100% .如何排查的呢 是通过 ...
- 记录一次线上bug
记录一次线上bug,总的来说就是弱网和重复点击.特殊值校验的问题. 测试场景一: 在3g网络或者使页面加载速度需要两秒左右的时候,输入学号,提交学生的缴费项目,提交完一个 学生的缴费后, ...
随机推荐
- Codeforces Global Round 13
比赛地址 A(水题) 题目链接 题目: 给出一个\(01\)序列,有2种操作:1.将某个位置取反:2.询问\(01\)序列中第\(k\)大的数 解析: 显然维护1的数目即可 #include<b ...
- Java开发不懂Docker,学尽Java也枉然,阿里P8架构师手把手带你玩转Docker实战
转: Java开发不懂Docker,学尽Java也枉然,阿里P8架构师手把手带你玩转Docker实战 Docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一 ...
- Get和Post请求方式
Get和Post是两种不同的类型的请求. 它们主要有3点不同. 1.get请求通过浏览器地址栏传递表单数据.post请求通过form data 传递数据,不会通过地址栏. 2.get请求安全性较低,p ...
- HDOJ-6621(线段树+二分法)
K-th Closest Distance HDOJ-6621 本题可以使用线段树解决,结点存本结点对应的所有元素,并按照从小打到排序 最后使用二分法求解答案.因为题目中有绝对值,所以需要使用两次查找 ...
- web服务器-并发服务器2
阅读目录 1.Web静态服务器-5-非堵塞模式 2.Web静态服务器-6-epoll 3.Web静态服务器-7-gevent版 4.知识扩展-C10K问题 一.Web静态服务器-5-非堵塞模式 单进程 ...
- 漏洞复现-ActiveMq反序列化漏洞(CVE-2015-5254)
0x00 实验环境 攻击机:Win 10 靶机也可作为攻击机:Ubuntu18 (docker搭建的vulhub靶场) 0x01 影响版本 Apache ActiveMQ 5.13.0之前 ...
- WPF 应用 - 通过 js 缩放 System.Windows.Controls.WebBrowser 的内容
1. 前提 原本是在大屏上展示系统,系统有个功能是加载第三方的网站,第三方网站按照大屏的分辨率写死了宽高: 现需要改到小屏展示系统,而这个第三方的网站不能随着 WebBrowser 窗口的尺寸调整网站 ...
- python文件操作以及循环小结
Python中的文件使用建议使用 with open(filename, "r") as f: 的形式进行文件操作,如果忘记关闭文件指针的话,他会帮你自己关闭文件, 如果使用原来的 ...
- EmEditor, 在正则使用()匹配后 使用$1 $2进行对括号内的值进行引用
$1表示第一个括号,$2表示第二个括号,以此类推
- 修改RedHat7的root用户密码
前言 前段时间由于长时间没有使用虚拟机里面的一个操作系统,导致密码记得不是太清,登录不进去.今天想起还是做个小记录,以便以后参考. 再一个是,当时网上也搜了很多解决问题的博客,但大部分都是同一个博客内 ...