百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

好难~记录一次生产上的OOM解决过程

csdh11 2025-03-10 14:36 1 浏览

点击上方,轻松关注!及时获取有趣有料的技术文章



记录一次生产上的OOM解决过程


一.项目架构

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 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;
  }

作者:听风是雨
原文地址:
https://www.cnblogs.com/july-sunny/p/12370615.html




上面就是本次文章的全部内容,感谢你的阅读,希望对你有帮助,也欢迎你点赞留言和转发~

关注我,不迷路,及时获取有趣有料内容,See you next good day~

相关推荐

Hutool Java工具类库导出Excel,超级简单

作者:程序猿的内心独白原文链接:http://suo.im/5Zxx2L前言在开发应用系统的时候,导出文件是必不可放的功能。以前用过POI、easyexcel等工具的导入导出功能,但总感觉太麻烦了,代...

java轻松玩转Excel之EasyExcel

项目地址:https://github.com/PiKeZhao/excel-model.git如果您对该项目有什么问题加群咨询哦978219630(各类电子书籍,学习视频等)大家常用Apache...

程序员:超级简单导出Excel 工具,Hutool Java工具类库

前言在开发应用系统的时候,导出文件是必不可放的功能。以前用过POI、easyexcel等工具的导入导出功能,但总感觉太麻烦了,代码特别多,感觉并不是很好用。今天给大家介绍一款新工具,java工具类库H...

EasyExcel导出Excel表格到浏览器,通过Postman测试导出Excel

一、前言小编最近接到一个导出Excel的需求,需求还是很简单的,只需要把表格展示的信息导出成Excel就可以了,也没有复杂的合并列什么的。...

好难~记录一次生产上的OOM解决过程

点击上方,轻松关注!及时获取有趣有料的技术文章记录一次生产上的OOM解决过程一.项目架构...

发现开源:替换Jenkins,支持多用户多语言部署平台Walle很震撼

溪云阁:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习。部署系统,从手动部署,到用jenkins,到阿里的自动化运维部署,一直在不断的变化姿势,好让自己舒服点。...

SpringBoot + EasyExcel 轻松实现百万级数据导入导出,用起来还优雅

01、背景介绍在实际的业务系统开发过程中,操作Excel实现数据的导入导出基本上是个非常常见的需求。...

10W 行级别数据的 Excel 导入优化记录

优质文章,及时送达作者:后青春期的Keatswww.cnblogs.com/keatsCoder/p/13217561.html需求说明项目中有一个Excel导入的需求:缴费记录导入...

easypoi导出Excel根据内容如何自动换行和自动调整行高

在使用easypoi导出EXCEL的时候,经常会遇到需要根据内容自动换行和自动调整行高的情况...

POI读取/生成Excel大文件,有高人吗?

最近在搞一个通用文件处理服务器,需要处理excel文件,但是有的excel文件大小超过3M,读取文件时直接导致jvm异常:gcoverheadlimitexceeded。原来poi读取excel...

如何轻松实现Excel动态列导出?Easypoi教程来袭!

EasyPoi简介EasyPoi是一款基于ApachePOI和jxls的Java开源框架,它可以用于快速创建Excel、Word、Pdf等复杂文档。Easypoi最大的特点是可以通过注解来实现对Ex...

Excel导出,Excel模板导出Excel导入,Word模板导出神器-Easypoi

概述Easypoi是码云上的一个开源项目。项目开发组织是Lemur开源。目前属于码云最有价值开源项目。Easypoi是对poi的封装,其主打的功能就是容易,让一个没见接触过poi的人员就可以方便...

优雅地实现EasyPoi动态导出列的两种方式

前言嗨,大家好,我是希留。...

easypoi一款方便快捷的excel处理框架,赶紧学习起来

easypoi是一个基于ApachePOI和jxls封装的Java框架,用于简化Excel导入导出和Word导出等操作。它支持将Java对象转换为Excel或Word文档,并支持Excel模板导出和...

EasyPoi使用

EasyPoi的主要特点:1.设计精巧,使用简单2.接口丰富,扩展简单3.默认值多,writelessdomore4.springmvc支持,web导出可以简单明了使用1.easypoi...