一次生产环境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 隔离容器的运行环境 ...
随机推荐
- 庖丁解牛:最全babel-plugin-import源码详解
庖丁解牛:最全 babel-plugin-import 源码详解 序言:在用 babel-plugin 实现按需加载一文中笔者用作用域链思路实现了按需加载组件.此思路是统一式处理,进入 ImportD ...
- 使用Java读取Excel文件数据
通过编程方式读取Excel数据能实现数据导入.批量处理.数据比对和更新等任务的自动化.这不仅可以提高工作效率还能减少手动处理的错误风险.此外读取的Excel数据可以与其他系统进行交互或集成,实现数据的 ...
- Python 潮流周刊第 19 期摘要
原文全文:https://pythoncat.top/posts/2023-09-09-weekly 文章&教程 1.Mojo 终于提供下载了! 2.我们能从 PEP-703 中学到什么? 3 ...
- Java中的基本数据类型和包装类型的这些知识,你都知道吗?
Java中的基本数据类型和包装类型 Java 中的基本数据按类型可以分为四大类:布尔型.整数型.浮点型.字符型: 这四大类包含 8 种基本数据类型. 布尔型:boolean 整数型:byte.shor ...
- HTTP请求头引发的注入问题 (SQL注入)
关于请求头中注入问题的演示,这里我写了一些测试案例,用来测试请求头中存在的问题.我们常见的会发生注入的点有 Referer.X-Forwarded-For.Cookie.X-Real-IP.Accep ...
- 遥感图像处理笔记之【Deep learning for Geospatial data applications — Multi-label Classification】
遥感图像处理学习(2) 前言 遥感图像处理方向的学习者可以参考或者复刻 本文初编辑于2023年12月14日 2024年1月24日搬运至本人博客园平台 文章标题:Deep learning for Ge ...
- 推荐一款开源的Diffy自动化测试框架
1. 前言 软件测试是软件开发生命周期一个十分重要的环节,测试工作开展的好坏,很大程度上决定了产品质量的好坏,但软件产品随着版本的持续迭代,功能日益增多,系统愈加复杂,而从质量保障的角度,除了要保障好 ...
- 教你轻松用上ChatGPT
最近ChatGPT大火呀,小伙伴们是不是在网上看到各种和ChatGPT有趣聊天的截图,奈何自己实力不够,被网络拒之门外,只能眼馋别人的东西.看别人玩,肯定不如自己玩一把舒服的啊.今天小卷就给大家汇总了 ...
- 小知识:Flex ASM特性对集群资源显示的影响
有客户咨询,认为19c RAC集群资源状态和11g RAC大不一样,比如在他们的19c集群,也是只部署2节点,却显示3个资源状态,其中第三个还是offline状态,担心是否有影响. 实际上这和Flex ...
- BeginCTF 2024(自由赛道)MISC
real check in 题目: 从catf1y的笔记本中发现了这个神秘的代码 MJSWO2LOPNLUKTCDJ5GWKX3UN5PUEM2HNFXEGVCGL4ZDAMRUL5EDAUDFL5M ...