系统gc后线程数增加原因分析过程

简介: 问题&现象1、由于系统过一段时间(四五天)commited old区会增大,我们应用中增加每天凌晨一次主动fullgc的任务,但是观察下来发现每天经过system.gc后线程数会增加几个,一直增加到接近300不会增加,并且增加的线程为守护线程。监控图如下:2、某些机器偶然出现线程数陡增情况:分析第一反应为fullgc时会新建gc线程去处理,但是通过jstack指令监控两天的线程变化发现,g

问题&现象

1、由于系统过一段时间(四五天)commited old区会增大,我们应用中增加每天凌晨一次主动fullgc的任务,但是观察下来发现每天经过system.gc后线程数会增加几个,一直增加到接近300不会增加,并且增加的线程为守护线程。监控图如下:

2、某些机器偶然出现线程数陡增情况:

分析

第一反应为fullgc时会新建gc线程去处理,但是通过jstack指令监控两天的线程变化发现,gc线程名字和数量并没有发生变化,通过jstack两天的线程栈发现,每天增加的是tomcat生成的业务处理线程,http-nio-8080-exec-22,增加的数量也和jstack一样每天大概有四到五个;

重点是如下图,增加的6个线程如下:

这时会有一个疑问是为什么是daemon线程呢,因为守护线程随着jvm的销毁才会销毁,我们跟着springboot内嵌的tomcat源码看起,详细的加载流程和处理请求参考以下文章:springboot加载tomcat

简单说,tomcat内部也有一个类似selector角色去处理有请求的链接,一旦有read和write数据后会交给一个线程池去处理请求;

代码跟踪链路:Connector.startInternal()-->AbstractProtocol.start()-->endpoint.start()--

NioEndpoint.startInternal()-->AbstractEndpoint.createExecutor(),初始化线程池代码如下:

跟踪代码发现初始化核心线程数为10,最大为200,队列为无界队列,守护线程,为什么是守护线程,源码解释如下:主要原因是业务处理线程不销毁是为了减少新建线程带来的性能问题,也减少维护线程长期存活带来的问题

另外tomcat自己实现了线程队列,并不是jdk处理流程那样先判断核心再判断队列是否满,入队列 队列满了新增非核心线程,而是优先新建最大线程数量去处理,当没有线程处理时会放入队列;源码如下:

此时执行的threadpoolexecuter是tomcat自己实现的,但是execute执行调用jdk的方法

重点看上面图中workquue.offer方法,实际是先判断是否达到最大线程数,达到之后才会放入队列中。

如果线程池为空则使用父类线程池执行

如果线程池线程数量等于于最大线程数,进入队列

如果提交的任务数量小于运行的线程数,进入队列

如果线程数小于最大线程数,则返回false,上层代码会尝试创建新的线程

回过头来回答上面提出的问题,

  1. 因为systemgc会触发g1的fullgc,使用serial old来暂停业务线程去处理,如果此时有请求进来,发现没有可用线程可以处理,就会新增线程去处理请求,直到写入队列;
  2. 日常流量较大时也会存在业务线程不够用时,会尝试去创建多的线程,所以一半业务线程可以设置大一些,一般两核默认200个线程。

结论

因为目前看下来最大线程数设置为200,且现在200多台机器里业务处理最大线程数为81远远低于200,暂时没出现因为线程数暴增带来的问题,所以目前暂时继续观察。但是这种问题带给我们的思考和重视也要有的,例如如何避免出现大流量导致线程不够用等情况,所以我们还要采取一定的预警方案来提前发现问题和解决问题。

  1. 短期方案:增加监控报警:线程数超过380时报警
  2. 长期方案一:如果流量过大超过200线程数,可以适当对tomcat线程数进行调优,根据压测qps rt cpu等进行设置,一般最大线程数可以设置为n*200,n为cpu核数
  3. 长期方案二:增加单机和集群限流机制,例如接入sentinel等方式
  4. 紧急方案:一旦出现问题立即重启机器

参考文章

tomcat线程加载

springboot加载tomcat

相关文章
|
2月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
40 1
|
12天前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
2月前
|
监控 负载均衡 算法
线程数突增!领导说再这么写就GC掉我:深入理解与优化策略
【8月更文挑战第29天】在软件开发的世界里,性能优化总是开发者们绕不开的话题。特别是当面对“线程数突增”这样的紧急情况时,更是考验着我们的技术功底和问题解决能力。今天,我们就来深入探讨这一话题,分享一些工作学习中积累的技术干货,帮助大家避免被“GC”(垃圾回收,也常用来幽默地表示“被炒鱿鱼”)的尴尬。
37 2
|
2月前
|
存储 监控 Java
|
2月前
|
安全 Java 开发者
Swing 的线程安全分析
【8月更文挑战第22天】
36 4
|
2月前
|
Java 数据库连接 数据库
当线程中发生异常时的情况分析
【8月更文挑战第22天】
69 4
|
2月前
|
安全 Java 程序员
线程安全与 Vector 类的分析
【8月更文挑战第22天】
24 4
|
2月前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
1月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
15 0
|
2月前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践