问题发现

早上过来,饭都没来的及吃,运维就给我发来信息,说是某个接口调用大量超时。因为最近这个接口调用量是翻倍了,所以我就去检查了下慢SQL,发现确实是有较多的慢SQL,所以我就缩减了查询的时间范围,但是效果并不好。
过了一会发现,这个服务fullGC是有问题的,太频繁了,这个应该是导致接口超时的根本问题,因为时间也是对的上的。
这个是最近三个小时fullGC的监控图:
这个是最近三天fullGC的监控图:
对比一下,就不难发现,fullGC数量是从3月15号晚上9点开始增加的,也是这个接口对外开放的时间。
 

解决思路

1、首先去服务器上面下载dump文件,分析是哪里造成了内存泄漏,频繁触发fullGC。首先找出服务器内java文件的PID,然后保存dump文件,我们公司java服务是固定端口号:1

使用top命令:
然后执行命令:jmap -dump:file=202303160924.dump,format=b 1 ,保存dump文件

2、根据dump文件,分析出堆内对象的分布情况

    • 下载一个可以分析dump文件的工具,这里我下载是Jprofiler
    • 查看大对象的分析,发现是java.lang.ApplicationShutdownHooks的hooks占用太大内存,并且得知改熟悉是一个Map
    • 分析这个Map里面的元素引用关系,可以看到这个map里面存的都是线程对象,并且大部分都是一个名为java.util.concurrent.ScheduledThreadPoolExecutor@59648a61的线程池对象,到了这里就定位到问题代码了,是这次新加的接口里面有一个异步操作,用的guava并发包里面的一个超时等待功能的接口,具体思路就是启用一个定时任务线程池去定时去检查在规定时间内,是否返回结果。
  

3、看看我的业务代码是哪里出现了问题

  1. //异步执行某个查询方法,并且在规定时间内返回查询结果
  2. public <T> T asyncWithTimeout(ScheduledThreadPoolExecutor executor, Callable<T> callable,
  3. long time, TimeUnit unit) {
  4. try {
  5. ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(threadPoolExecutor);
  6. ListenableFuture<T> future = listeningExecutorService.submit(callable);
  7. //这里是创建一个定时任务线程,去定时检查是否在规定时间内查询完毕,应该就是这个去添加了钩子函数,进去看看
  8. ScheduledExecutorService scheduledExecutorService = MoreExecutors.getExitingScheduledExecutorService(executor);
  9. return Futures.withTimeout(future, time, unit, scheduledExecutorService).get(time, unit);
  10. } catch (InterruptedException | ExecutionException | TimeoutException e) {
  11. log.warn("异步方法执行失败,error:{}", e.getMessage());
  12. }
  13. return null;
  14. }
  15. //=======================guava并发包代码=======================
  16. @Beta
  17. @GwtIncompatible // TODO
  18. public static ScheduledExecutorService getExitingScheduledExecutorService(
  19. ScheduledThreadPoolExecutor executor) {
  20. //每次都去创建一个新的对象
  21. return new Application().getExitingScheduledExecutorService(executor);
  22. }
  23. final ScheduledExecutorService getExitingScheduledExecutorService(
  24. ScheduledThreadPoolExecutor executor) {
  25. return getExitingScheduledExecutorService(executor, 120, TimeUnit.SECONDS);
  26. }
  27. final ScheduledExecutorService getExitingScheduledExecutorService(
  28. ScheduledThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) {
  29. useDaemonThreadFactory(executor);
  30. ScheduledExecutorService service = Executors.unconfigurableScheduledExecutorService(executor);
  31. //添加构造函数的地方,进去看看
  32. addDelayedShutdownHook(executor, terminationTimeout, timeUnit);
  33. return service;
  34. }
  35. final void addDelayedShutdownHook(
  36. final ExecutorService service, final long terminationTimeout, final TimeUnit timeUnit) {
  37. checkNotNull(service);
  38. checkNotNull(timeUnit);
  39. //继续点进去
  40. addShutdownHook(
  41. MoreExecutors.newThread(
  42. //线程名字对上了,就在对象引用的截图里面出现过
  43. "DelayedShutdownHook-for-" + service,
  44. new Runnable() {
  45. @Override
  46. public void run() {
  47. try {
  48. // We'd like to log progress and failures that may arise in the
  49. // following code, but unfortunately the behavior of logging
  50. // is undefined in shutdown hooks.
  51. // This is because the logging code installs a shutdown hook of its
  52. // own. See Cleaner class inside {@link LogManager}.
  53. service.shutdown();
  54. service.awaitTermination(terminationTimeout, timeUnit);
  55. } catch (InterruptedException ignored) {
  56. // We're shutting down anyway, so just ignore.
  57. }
  58. }
  59. }));
  60. }
  61. @VisibleForTesting
  62. void addShutdownHook(Thread hook) {
  63. Runtime.getRuntime().addShutdownHook(hook);
  64. }
  65. //=======================guava并发包代码=======================
  66. public void addShutdownHook(Thread hook) {
  67. SecurityManager sm = System.getSecurityManager();
  68. if (sm != null) {
  69. sm.checkPermission(new RuntimePermission("shutdownHooks"));
  70. }
  71. //定位到问题了,就是这里添加的钩子函数
  72. ApplicationShutdownHooks.add(hook);
  73. }
  74. static synchronized void add(Thread hook) {
  75. if(hooks == null)
  76. throw new IllegalStateException("Shutdown in progress");
  77. if (hook.isAlive())
  78. throw new IllegalArgumentException("Hook already running");
  79. if (hooks.containsKey(hook))
  80. throw new IllegalArgumentException("Hook previously registered");
  81. //存在到 hooks 这个map对象里面,就是这个大对象
  82. hooks.put(hook, hook);
  83. }

问题解决

经过上面问题的排查,造成hooks大对象的原因找到了,就是每次调用接口的时候,都会往hooks里面put一个对象。
所以,解决办法很简单,就是不用每次都去生成一个ScheduledExecutorService对象,类初始化的时候创建一次就行了
改造后的代码如下:
  1. private ListeningExecutorService listeningExecutorService;
  2. private ScheduledExecutorService scheduledExecutorService;
  3. public static AsyncUtils getInstance() {
  4. return ThreadHolder.INSTANCE.getAsyncWithCallback();
  5. }
  6. @SuppressWarnings("UnstableApiUsage")
  7. private AsyncUtils() {
  8. listeningExecutorService = MoreExecutors.listeningDecorator(ThreadPoolConstant.THREAD_POOL_EXECUTOR);
  9. scheduledExecutorService = MoreExecutors.getExitingScheduledExecutorService(ThreadPoolConstant.SCHEDULED_THREAD_POOL_EXECUTOR);
  10. }
  11. @SuppressWarnings("UnstableApiUsage")
  12. public <T> T asyncWithTimeout(Callable<T> callable,
  13. long time, TimeUnit unit) {
  14. try {
  15. ListenableFuture<T> future = listeningExecutorService.submit(callable);
  16. return Futures.withTimeout(future, time, unit, scheduledExecutorService).get(time, unit);
  17. } catch (InterruptedException | ExecutionException | TimeoutException e) {
  18. log.warn("异步方法执行失败,error:{}", e.getMessage());
  19. }
  20. return null;
  21. }
  22. private enum ThreadHolder {
  23. /**
  24. * 线程持有类 INSTANCE
  25. */
  26. INSTANCE;
  27. private final AsyncUtils asyncUtils;
  28. ThreadHolder() {
  29. asyncUtils = new AsyncUtils();
  30. }
  31. public AsyncUtils getAsyncWithCallback() {
  32. return asyncUtils;
  33. }
  34. }
 

记一次生产频繁发生FullGC问题的更多相关文章

  1. Kafka 异步消息也会阻塞?记一次 Dubbo 频繁超时排查过程

    线上某服务 A 调用服务 B 接口完成一次交易,一次晚上的生产变更之后,系统监控发现服务 B 接口频繁超时,后续甚至返回线程池耗尽错误 Thread pool is EXHAUSTED.因为服务 B ...

  2. 记一次生产主机中挖矿病毒"kintegrityds"处理过程!

    [记一次生产挖矿病毒处理过程]: 可能性:webaap用户密码泄露.Jenkins/redis弱口令等. 1.监控到生产主机一直load告警 2.进服务器 top查看进程,发现挖矿病毒进程,此进程持续 ...

  3. 记一次生产数据库"意外"重启的经历

    前言 在一个阳光明媚的下午,电脑右下角传来一片片邮件提醒,同时伴随着微信钉钉的震动,打开一看,应用各种出错,天兔告警,数据库服务器内存爆红,Mysql数据库实例挂掉了. 排查 先交代一下数据库版本: ...

  4. 记一次生产环境axis2服务特别慢的问题。

    情况如下: 某服务,在测试环境测试的时候整个响应过程也就0.5s左右,测试环境和生产环境axis2版本一致,tomcat版本一致,但是生产环境需要差不多20S. 后来,越来越慢,导致服务一起来,整个生 ...

  5. 记一次生产环境thrift服务的配置问题

    问题现象 有客户反馈我们的产品有时反应很慢,处理会出现超时. 问题分析过程 1.第一反应可能是用户增加,并发量太大了,询问了运营,最近用户注册数据并没有猛增. 2.分析access日志,发现有隔一段时 ...

  6. 记一次生产mysql数据误操作恢复过程

    提示:建议每次对数据库进行修改时都做下备份 注意:以下Mysql开启的是row格式的binlog日志,确定到误操作具体时间可能有些麻烦,默认的格式就能很快找出来.这里开启row的原因是还有一种更快的方 ...

  7. 记一次生产发版时SpringBoot服务停用启用的问题

    近期项目交接,接手了个SpringBoot项目.生产环境里,jar包是通过软链接做成linux服务来启动和停用. 然而,每次通过jenkins构建发版,项目构建完毕,还要手动再去重启服务. 听交接的同 ...

  8. 记一次生产kafka消息消费的事故

    事故背景: 我们公司与合作方公司有个消息同步的需求,合作方是消息生产者,我们是消息消费者,他们通过kafka给我们推送消息,我们实时接收,然后进行后续业务处理.昨天上午,发现他们推送过来的广场门店信息 ...

  9. 记一次生产环境tomcat线程数打满情况分析

    前言 旨在分享工作中遇到的各种问题及解决思路与方案,与大家一起学习. -- 学无止境, 加油 ! Just do it ! 问题描述 运行环境描述 tomcat-8.5 单节点(该应用集群20个节点) ...

  10. 记一次生产事故的排查与优化——Java服务假死

    一.现象 在服务器上通过curl命令调用一个Java服务的查询接口,半天没有任何响应.关于该服务的基本功能如下: 1.该服务是一个后台刷新指示器的服务,即该服务会将用户需要的指示器数据提前计算好,放入 ...

随机推荐

  1. holiday11

    holiday11--linux basis From today I will write my note in English ,hope I will stick to it. user and ...

  2. UniCode 下char*转CString ,利用MultiByteToWideChar进行转换,中文乱码的解决方案

    //计算char *数组大小,以字节为单位,一个汉字占两个字节 int charLen = strlen(sText); //计算多字节字符的大小,按字符计算. int len = MultiByte ...

  3. HTML复习(17.表格样式)

    重点 掌握caption-side(表格标题位置) 掌握border-collapse(表格边框合并) 掌握border-spacing(表格边框间距) 表格标题位置在CSS中,我们可以使用capti ...

  4. GBDT中损失函数的负梯度用来拟合的一些理解

    将\(L(y_i,f(x_i))\)在\(f(x_i)=f_{m-1}(x_i)\)处泰勒展开到一阶(舍去余项,故为近似) \[L(y_i,f(x_i))\approx L(y_i,f_{m-1}(x ...

  5. 使用bat脚本判断远程SVN文件是否有修改

    在Windows上, 使用 svn status -u -q %dir% 可以列出svn仓库的状态: M 8295 build.bat * 8306 E:\game\bzk\dev\tools\pro ...

  6. Array of products

    refer to: https://www.algoexpert.io/questions/Array%20Of%20Products Problem Statement Sample input A ...

  7. Python+Django(1)——建立项目

    为项目新建一个目录,将其命名为learning_log,再在终端中切换到这个目录(Python 3): 运行模块venv 来创建一个名为ll_env的虚拟环境:python -m venv ll_en ...

  8. Linux_ZABBIX实战

    typora-copy-images-to: img ZABBIX实战 zabbix安装 Zabbix详解 zabbix中文社区: http://www.zabbix.org.cn/ Zabbix中文 ...

  9. vue 高级部分

    props的其它内容 props的作用就是用于在子组件中接收传入的数据 props的使用方式 1.数组 props:['name'] 2.对象,指定传入变量的类型 props:{name:Number ...

  10. hdu:排列组合(指数型母函数)

    Problem Description有n种物品,并且知道每种物品的数量.要求从中选出m件物品的排列数.例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有"AB",&q ...