问题发现

早上过来,饭都没来的及吃,运维就给我发来信息,说是某个接口调用大量超时。因为最近这个接口调用量是翻倍了,所以我就去检查了下慢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. MAC使用Graphviz包报错 failed to execute PosixPath('dot')

    在使用LightGBM进行可视化时,用到了Graphviz包,在安装Graphviz包时遇到了以下问题. 错误描述: ExecutableNotFound: failed to execute Pos ...

  2. Centos7编译Nginx1.19.0笔记

    下载Nginx安装包 官网下载页:http://nginx.org/en/download.html 终端输入: # 安装依赖yum -y install wget gcc gcc-c++ autoc ...

  3. not eligible for getting processed by all BeanPostProcessors

    描述 这个BUG大的起源是我上线以后,在后台看日志的时候发现一行奇怪的INFO日志: 2022-06-09 23:34:24 [restartedMain] [org.springframework. ...

  4. Surface Pro (1796),安装Win11之后出现的问题。

    2020年年中我为了体验微软新的操作系统,加入了Review计划,即:Windows预览体验计划.然后升级到了Win11.由于苏菲一直是作为辅助设备用,所以平时就是开节电模式,中途也升级了几次Win1 ...

  5. logic and control

    logic and control 程序的本质 Programs = Data Structures + Algorithms Algorithm = Logic + Control CPU 的构成 ...

  6. 为动态二级域名申请https的免费证书.

    前面已经讲过将nginx部署,并注册了免费的二级域名.但将网址发给儿子,儿子说微信已经不能打开http的网址了,所以一想还是研究一下https的证书申请. 网上有很多讲通过,acme的脚本来自动化申请 ...

  7. modesim : mosel not define

    这个星期仿真sdram时,添加的sdram 模型文件在modesim中总是找不到,在qq群里提问和私聊别人都没有得到解决方法.于是自己摸索了好多天.终于得到了解决. 可以看到modesim里面work ...

  8. 请求GET和POST的区别

    实际上GET和POST他们只有语义上的区别,之所以有我们所谓的区别是由于前端与后端达成的协议. 区别1:get请求不会附带请求体,而post请求有请求体. 区别2: get请求传递的信息量是有限的,适 ...

  9. jmeter 变量的使用

    jmeter添加变量 一.添加用户自定义变量 添加用户自定义变量 作用:常用数据参数化.当变量发生变化时,不需要逐个脚本修改,只需要修改用户自定义中的变量就可以了. 变量使用如下图 二.函数助手定义变 ...

  10. ABP vNext微服务架构详细教程(补充篇)——单层模板

    1. 简介 在之前的<ABP vNext微服务架构详细教程>系列中,我们已经构建了完整的微服务架构实例,但是在开发过程中,我们会发现每个基础服务都包含10个类库,这是给予DDD四层架构下A ...