问题发现

早上过来,饭都没来的及吃,运维就给我发来信息,说是某个接口调用大量超时。因为最近这个接口调用量是翻倍了,所以我就去检查了下慢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、看看我的业务代码是哪里出现了问题

//异步执行某个查询方法,并且在规定时间内返回查询结果
public <T> T asyncWithTimeout(ScheduledThreadPoolExecutor executor, Callable<T> callable,
long time, TimeUnit unit) {
try {
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(threadPoolExecutor);
ListenableFuture<T> future = listeningExecutorService.submit(callable);
//这里是创建一个定时任务线程,去定时检查是否在规定时间内查询完毕,应该就是这个去添加了钩子函数,进去看看
ScheduledExecutorService scheduledExecutorService = MoreExecutors.getExitingScheduledExecutorService(executor);
return Futures.withTimeout(future, time, unit, scheduledExecutorService).get(time, unit);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.warn("异步方法执行失败,error:{}", e.getMessage());
}
return null;
} //=======================guava并发包代码======================= @Beta
@GwtIncompatible // TODO
public static ScheduledExecutorService getExitingScheduledExecutorService(
ScheduledThreadPoolExecutor executor) {
//每次都去创建一个新的对象
return new Application().getExitingScheduledExecutorService(executor);
} final ScheduledExecutorService getExitingScheduledExecutorService(
ScheduledThreadPoolExecutor executor) {
return getExitingScheduledExecutorService(executor, 120, TimeUnit.SECONDS);
} final ScheduledExecutorService getExitingScheduledExecutorService(
ScheduledThreadPoolExecutor executor, long terminationTimeout, TimeUnit timeUnit) {
useDaemonThreadFactory(executor);
ScheduledExecutorService service = Executors.unconfigurableScheduledExecutorService(executor);
//添加构造函数的地方,进去看看
addDelayedShutdownHook(executor, terminationTimeout, timeUnit);
return service;
} final void addDelayedShutdownHook(
final ExecutorService service, final long terminationTimeout, final TimeUnit timeUnit) {
checkNotNull(service);
checkNotNull(timeUnit);
//继续点进去
addShutdownHook(
MoreExecutors.newThread(
//线程名字对上了,就在对象引用的截图里面出现过
"DelayedShutdownHook-for-" + service,
new Runnable() {
@Override
public void run() {
try {
// We'd like to log progress and failures that may arise in the
// following code, but unfortunately the behavior of logging
// is undefined in shutdown hooks.
// This is because the logging code installs a shutdown hook of its
// own. See Cleaner class inside {@link LogManager}.
service.shutdown();
service.awaitTermination(terminationTimeout, timeUnit);
} catch (InterruptedException ignored) {
// We're shutting down anyway, so just ignore.
}
}
}));
} @VisibleForTesting
void addShutdownHook(Thread hook) {
Runtime.getRuntime().addShutdownHook(hook);
} //=======================guava并发包代码======================= public void addShutdownHook(Thread hook) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("shutdownHooks"));
}
//定位到问题了,就是这里添加的钩子函数
ApplicationShutdownHooks.add(hook);
} static synchronized void add(Thread hook) {
if(hooks == null)
throw new IllegalStateException("Shutdown in progress"); if (hook.isAlive())
throw new IllegalArgumentException("Hook already running"); if (hooks.containsKey(hook))
throw new IllegalArgumentException("Hook previously registered"); //存在到 hooks 这个map对象里面,就是这个大对象
hooks.put(hook, hook);
}

问题解决

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

记一次生产频繁发生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. 2.TS 的数据类型

    一.定义变量 TS 中创建变量有四个要素: 定义变量的关键字:var.let.const 变量名 变量的数据类型 变量的初始值 语法结构如下: let 变量名: 数据类型 = 初始值 注意,变量的初始 ...

  2. Java+selenium自动爬取网站内容并写入本地

    目的:本文主要描述如何使用Java+selenium爬取58同城招聘页,并记录指定职位的招聘公司名保存到本地 一.首先创建一个maven工程,配置依赖包 1 <dependencies> ...

  3. docker部署服务器

    Docker部署PostGres docker run -d --name postgres --restart always -e POSTGRES_USER='postgres' -e POSTG ...

  4. 从redis中取出数据并转成java对象

    1.//数据存入redis中 redisTemplate.opsForValue().set(loginame, JSON.toJSONString(users),1000,TimeUnit.SECO ...

  5. elementUI table 第一列值相等合并

    效果如下: 首先:在table上加:span-method="spanMethod" 其次: methods中加两个方法 dataPretreatment() { //表格数据列合 ...

  6. Python基础数据类型-Dictionary(字典)

    # -- coding: utf-8 -- # @time : 2022/7/19 21:51 # @file : 10pytest基本数据类型-dic.py # @software: pycharm ...

  7. 互联网架构 之备份服务器 rsync

    备份服务器(Rsync) 1.介绍备份的场景 1.什么情况下需要备份 一般数据比较重要的情况下,数据如果丢失很容易找不回来了的,建议备份. 2.什么是备份 将原有的数据复制一份或几份存起来,以备不时之 ...

  8. 点击按钮触发div颜色改变的几种写法

    目录 JavaScript 行内事件 onclick绑定 关于选取元素 关于改变颜色 addEventListener jQuery 获取元素 绑定事件 设置样式 css() 添加class Vue ...

  9. RabbitMQ问题汇总

    内网通过代理服务器访问MQ服务器,无法连接 使用的是socket连接,要设置socket代理,而不是http代理 Properties prop = System.getProperties(); / ...

  10. pycharm激活教程

    激活地址:https://shimo.im/docs/YW9Gc3vHhJkWtRyQ/read 细节注意:下载版本,在官网下载,选择左下角更多版本,下载2019,3月份版本