问题发现

早上过来,饭都没来的及吃,运维就给我发来信息,说是某个接口调用大量超时。因为最近这个接口调用量是翻倍了,所以我就去检查了下慢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. 分布式接口幂等性、分布式限流:Guava 、nginx和lua限流

    接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用. 举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此 ...

  2. SQL-分组聚合-子查询

    -- having前面必须有group byselect e.deptno ,sum(e.sal) as sum_sal ,min(e.sal) as min_sal ,max(e.sal) as m ...

  3. 一个基于线程池和epoll的IO事件管理器

    前面几篇博客介绍了Epoll, ThreadPool, 其中 Epoll 封装了epoll的各类api, 可在epoll句柄中添加/修改/删除 fd 的 各类事件(EPOLLIN | EPOLLOUT ...

  4. Vscode,php运行

    1.下载好vscode,点击左侧扩展,然后搜索php,安装插件 2.打开小皮面板创建网站 点击确认 创建成功 3.浏览器输入http://myblog验证 4.在vscode打开新建的myblog文件 ...

  5. 使用python脚本批量造数据

    本篇将采用 Python 脚本的方式进行批量给mysql造数据. 为了使 Python 可以连上数据库(MySQL),并且可以与数据库交互(增删改查等操作),则需要安装 MySQL 客户端操作库.  ...

  6. fpga 通过vga模块使电脑显示图片

    对vga模块搞了好几天,vga接口时序不难,就是行计数,列计数,对应到每一个像素上去.这里我先把vga控制模块的程序付上.采用的是小梅哥的程序.屏幕是800X600,配置的时钟是40M.注意改变屏幕尺 ...

  7. spring-boot-starter-webflux 与spring-cloud-starter-openfeign冲突

    Thu Oct 22 17:16:01 CST 2020 [3be84a1c-14] There was an unexpected error (type=Internal Server Error ...

  8. 将SNAPSHOT包上传到Nexus私服

    首先确定要上传的仓库的Type为hosted,Policy为Snapshot 上传命令为: mvn deploy:deploy-file -DgroupId=com.ctg.ag -Dartifact ...

  9. Selenium 自动化中实现双击操作

    在selenium中,以name定位为例,单击元素的代码为:driver.find_element_by_name("name").click(),那么,实现双击操作的代码能不能写 ...

  10. docker知识篇

    什么是Docker?Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的.可移植的.自给自足的容器.开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机).b ...