问题发现

早上过来,饭都没来的及吃,运维就给我发来信息,说是某个接口调用大量超时。因为最近这个接口调用量是翻倍了,所以我就去检查了下慢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. java 泛型使用

    泛型类 // 简单泛型 class Point<T>{ // 此处可以随便写标识符号,T是type的简称 private T var ; public T getVar(){ return ...

  2. Study python_03

    函数 基本思想---函数是用来重复使用的 def shili(input_): print("我了个去 %s"%input_) shili('你竟然') 当一个函数中即有默认参数, ...

  3. opencv基本函数详解笔记

    一.读取保存图片 Mat scrImage = imread("1.jpg"); //显示图像 imshow("原图", scrImage); //窗口等待 w ...

  4. php处理mysql的结果集

    Php使用mysqli_result类处理结果集有以下几种方法 fetch_all() 抓取所有的结果行并且以关联数据,数值索引数组,或者两者皆有的方式返回结果集. fetch_array() 以一个 ...

  5. 【python】读取nc文件

    读取nc文件前的准备,安装一些库 1.先把几个用到的库下载 Cartopy 简介与安装(转载) - 简书 (jianshu.com) Python Extension Packages for Win ...

  6. linux 基础命令 apt

    Linux apt 命令 apt(Advanced Packaging Tool)是一个在 Debian 和 Ubuntu 中的 Shell 前端软件包管理器. apt 命令提供了查找.安装.升级.删 ...

  7. Leetcode 199

    199. Binary Tree Right Side View Given the root of a binary tree, imagine yourself standing on the r ...

  8. docker出现“Failing to start dockerd: failed to create NAT chain DOCKER”错误

    使用Windows的WSL 2里面的Ubuntu安装docker之后,启动docker服务一直失败,提示Docker is not running.使用dockerd命令会出现如下错误: INFO[2 ...

  9. 3DMAX2023卸载方法,如何完全彻底卸载删除清理干净3dmax各种残留注册表和文件?【转载】

    3dmax2023卸载重新安装方法,使用清理卸载工具箱完全彻底删除干净3dmax2023各种残留注册表和文件.3dmax2023显示已安装或者报错出现提示安装未完成某些产品无法安装的问题,怎么完全彻底 ...

  10. MySQL查询和事务

    数据库关联查询 内连接查询(inner join) SELECT * FROM tb1 INNER JOIN tb2 ON 条件 左表查询(左关联查询)(left join) 查询两个表共有的数据,和 ...