一次生产环境OOM排查
一、背景
前几天下午飞书告警群里报起了java.lang.OutOfMemoryError: unable to create new native thread告警,看见后艾特了对应的项目负责人但是负责人说没时间,无奈自己亲自上阵。
二、事情经过
2.1 问题排查
从报错信息就可以看出是服务申请不到足够的内存去创建新的线程导致的,熟悉JVM的都知道线程用的是虚拟机栈内存,这里是虚拟机栈内存不够跟堆没有关系。
然后立马登录阿里云进入容器使用如下命令dump出线程堆栈
jstack -l 1 > dump.log
让运维帮我把文件下载下来后,就把dump.log上传到网站https://fastthread.io/进行分析,分析结果如下:

可以看到服务的线程数竟然高达2000多个!!!而且也给出来警告这么高的线程数可能导致OOM,那么问题原因就很清楚了就是线程数过多导致的,那么是哪些线程过多呢?

第二张图显示数量做多的是名字为com.alibaba.nacos.client.Worker的线程,并且排行前三名都是nacos的线程,我一度怀疑是nacos出bug了
再点进去看看对应的线程堆栈如下图:

可以看出线程阻塞在从队列获取任务那里,说明这些线程都是没活干,空闲挂起的,具体看不出问题原因,只能看代码了。
打开项目,使用idea全局搜索com.alibaba.nacos.client.Worker,发现是ClientWorker类在构造方法里创建了一个定时线程池,线程名称就叫com.alibaba.nacos.client.Worker,其中核心线程数是4。

继续跟踪源码,发现只有在NacosConfigService的构造方法里调用了ClientWorke的构造方法。

但是点击NacosConfigService的构造方法却发现没有调用,这是什么情况呢?
点击其父类com.alibaba.nacos.api.config.ConfigService查找引用可以看到有个NacosFactory的工厂类,原来ConfigService的是实例是通过工厂方法创建的,而工厂方法内部是通过反射创建的,如下图。


最终找到了业务类NacosConfigServiceImpl如下图:
@Slf4j
@Component
public class NacosConfigServiceImpl implements ConfigService {
/**
* nacos地址key
*/
public static final String SERVER_ADDR = "spring.cloud.nacos.config.server-addr";
/**
* nacos配置namespace
*/
public static final String CONFIG_NAMESPACE = "spring.cloud.nacos.config.namespace";
@Getter
@Value("${spring.cloud.nacos.config.group}")
private String groupId;
@Value("${" + SERVER_ADDR + "}")
private String nacosServerAddr;
@Value("${" + CONFIG_NAMESPACE + "}")
private String nacosConfigNamespace;
private ConfigService configService;
private static final long TIMEOUT_MILLIS = 3000L;
@PostConstruct
public void init() throws NacosException {
this.configService = getConfigService();
}
@Override
public void registerListener(String dataId, ConfigListener configListener) {
AssertUtil.that(StringUtils.isNotBlank(dataId), BasicErrorCode.PARAM_ERROR, "dataId empty");
AssertUtil.that(configListener != null, BasicErrorCode.PARAM_ERROR, "configListener null");
try {
//注册监听器
// 第一次创建
ConfigService configService = getConfigService();
configService.addListener(dataId, getGroupId(), new DefaultListener(dataId, configListener));
//首次更新配置
// 第二次创建
String configInfo = getConfigInfo(dataId);
configListener.receiveConfigInfo(configInfo);
log.info(LogUtil.message("registerListener success",
dataId, InvokeLineUtil.simplifiedClassName(configListener)));
} catch (Exception e) {
log.error(LogUtil.exceptionMessage("registerListener exception", e, dataId, configListener));
throw new ApiException(BasicErrorCode.CONFIG_ERROR, e.getMessage());
}
}
@Override
public String getConfigInfo(String dataId) {
try {
ConfigService configService = getConfigService();
return configService.getConfig(dataId, getGroupId(), TIMEOUT_MILLIS);
} catch (Exception e) {
log.error(LogUtil.exceptionMessage("getConfigInfo exception", e, dataId));
throw new ApiException(BasicErrorCode.CONFIG_ERROR, e.getMessage());
}
}
private ConfigService getConfigService() throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosServerAddr);
properties.put(PropertyKeyConst.NAMESPACE, nacosConfigNamespace);
// 调用工厂方法创建configService
ConfigService configService = NacosFactory.createConfigService(properties);
return configService;
}
}
当时看到上面代码我都惊呆了,既然已经在init()方法里初始化了属性configService,为什么还要在registerListener()方法里两次调用getConfigService()去创建两次configService实例呢?
然后找了下registerListener()方法的调用,发现多达50处!
问题原因大概就清楚了,nacos的configService设计上是作为单例去使用的,但是被滥用导致创建了很多线程池和线程把虚拟机栈内存耗尽,再申请创建新线程时就报了OOM。
2.2 相关疑问
此时同事小A提出疑问,registerListener()方法执行完了,configService对象就会被gc回收,对应的线程池不也是被回收了吗?
这个问题看似很有道理其实不然,对象能不能回收是看这个对象到GC Root还有没有可达路径,线程池的gc root其实是线程,对应的gc路径是thread->workers->线程池,因为那些nacos线程池也没有设置属性allowCoreThreadTimeOut(true),所以就算线程空闲了核心线程也不会回收。
如果我们要在方法里创建并使用线程池,用完一定要记得调用shutdown方法,这样线程池才会被回收,当然更推荐线程池作为单例bean注入Spring容器使用。
爱专研的同事A又说你这线程数也对不上啊,50x4x2=400也没到500个啊。
大家有兴趣的可以去看看源码,NacosConfigService的构造方法里的ServerListManager类内部也有一个线程池,而且前面线程数量排行图中第三名的com.alibaba.nacos.client.remote.worker线程也跟这个有关。
2.3 解决方案
知道了原因解决起来就很快了,NacosConfigServiceImpl#registerListener()方法里创建configSerivce的代码改成用单例属性就行了。
三、总结
在用第三方开源组件的时候还是要多看看文档和其源码,使用正确的姿势不要一上来就撸代码,否则很容易产生生产事故。
不过在排查问题的过程中也顺便看了下nacos服务端和客户端配置同步的实现,算还有点收获吧。
一次生产环境OOM排查的更多相关文章
- 生产环境OOM\死锁问题排查修复
OOM: 1.快速恢复业务:如果是集群中的一台机器故障,先隔离故障服务器:如果是多台,则根据Nginx转发策略,对该功能转发到单独的集群,与其他流量隔离,确保其他业务不受影响 2.收集内存溢出Dump ...
- 生产事故-记一次特殊的OOM排查
入职多年,面对生产环境,尽管都是小心翼翼,慎之又慎,还是难免捅出篓子.轻则满头大汗,面红耳赤.重则系统停摆,损失资金.每一个生产事故的背后,都是宝贵的经验和教训,都是项目成员的血泪史.为了更好地防范和 ...
- 生产环境下JAVA进程高CPU占用故障排查
问题描述:生产环境下的某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高. 问题分析:1,程序属于CPU密集型,和开发沟通过, ...
- 生产环境JAVA进程高CPU占用故障排查
问题描述:生产环境下的某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高. 问题分析:1,程序属于CPU密集型,和开发沟通过, ...
- 生产环境下JAVA进程高CPU占用故障排查---temp
问题描述:生产环境下的某台tomcat7服务器,在刚发布时的时候一切都很正常,在运行一段时间后就出现CPU占用很高的问题,基本上是负载一天比一天高. 问题分析:1,程序属于CPU密集型,和开发沟通过, ...
- 总结:利用asp.net core日志进行生产环境下的错误排查(asp.net core version 2.2,用IIS做服务器)
概述 调试asp.net core程序时,在输出窗口中,在输出来源选择“调试”或“xxx-ASP.NET Core Web服务器”时,可以看到类似“info:Microsoft.AspNetCore. ...
- 生产环境部署springcloud微服务启动慢的问题排查
今天带来一个真实案例,虽然不是什么故障,但是希望对大家有所帮助. 一.问题现象: 生产环境部署springcloud应用,服务部署之后,有时候需要10几分钟才能启动成功,在开发测试环境则没有这个问题. ...
- 生产出现oom问题,怎么排查?
生产出现oom问题,怎么排查? 1.使用dmesg命令查看系统日志 dmesg |grep -E 'kill|oom|out of memory',可以查看操作系统启动后的系统日志,这里就是查看跟 ...
- 一次生产 CPU 100% 排查优化实践
前言 到了年底果然都不太平,最近又收到了运维报警:表示有些服务器负载非常高,让我们定位问题. 还真是想什么来什么,前些天还故意把某些服务器的负载提高(没错,老板让我写个 BUG!),不过还好是不同的环 ...
- 理解Docker(6):若干企业生产环境中的容器网络方案
本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...
随机推荐
- 【python】SSTI模版注入
0x00 Python Vene环境及介绍 venv虚拟环境:创建和管理虚拟环境的模块 首先apt update更新一下包管理 安装你当前版本的python-venv 选择一个目录,安装venv虚拟 ...
- Python 探索性数据分析工具(PandasGUI,Pandas Profiling,Sweetviz,dtale)以及学术论文快速作图science.mplstyle
如果探索的数据集侧重数据展示,可以选PandasGUI:如果只是简单了解基本统计指标,可以选择Pandas Profiling和Sweetviz:如果需要做深度的数据探索,那就选择dtale. 1. ...
- C/C++ 数据结构与算法笔记
实现顺序表 #include <stdio.h> #include <stdlib.h> #define MaxSize 10 int Insert_Elem(int Arra ...
- Flask 框架:运用SocketIO实现WebSSH
Flask 框架中如果想要实现WebSocket功能有许多种方式,运用SocketIO库来实现无疑是最简单的一种方式,Flask中封装了一个flask_socketio库该库可以直接通过pip仓库安装 ...
- 仅1cm厚!华硕发布全球最薄13.3英寸笔记本
近日,华硕发布了新款Zenbook S 13 OLED,官方称其为世界最纤薄的13.3英寸OLED笔记本电脑. 据悉,这款电脑的厚度仅有1cm,重量也仅有1kg,相较其他同尺寸的笔记本,确实更加轻薄. ...
- 5 款轻松上手的开源项目「GitHub 热点速览」
大家都忙一年了,所以今天来点轻松的吧!就是那种拿来直接用.免费看的开源项目. 开源真是一个充满惊喜的宝库,很多开源软件比收费软件还好用,比如这款开箱即用的电视直播软件:my-tv,它免费.无广告.启动 ...
- 下载、安装Git并拷贝GitHub项目到本地的流程
本文介绍分布式开源版本控制系统Git的下载.安装,并基于Git实现克隆GitHub中项目代码的方法. Git是一款开源软件,因此我们直接在Git的官方下载地址下载最新版本的Git即可.其中,在 ...
- Excel如何核对同一行的两列数据是否一致
方法一 Ctrl+G 快捷键Ctrl+G,点击[定位条件],选择"行内容差异单元格",点击[确定]. 方法二 条件格式 逆向思维,先利用条件格式查找出相同的数据,筛选剔除相同的数据 ...
- Java21 + SpringBoot3使用Spring Security时如何在子线程中获取到认证信息
目录 前言 原因分析 解决方案 方案1:手动设置线程中的认证信息 方案2:使用DelegatingSecurityContextRunnable创建线程 方案3:修改Spring Security安全 ...
- UVALive7146 Defeat the Enemy
题目链接 题目 见链接. 题解 知识点:贪心,STL. 首先要保证我方军队能消灭对方军队才行,因此只要我们按攻击力从大到小排,对方按防御力从大到小排,从大到小遍历,用我方所有攻击力大于敌方目前防御力军 ...