系统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月前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
56 1
|
2月前
线程CPU异常定位分析
【10月更文挑战第3天】 开发过程中会出现一些CPU异常升高的问题,想要定位到具体的位置就需要一系列的分析,记录一些分析手段。
78 0
|
4月前
|
监控 Java 测试技术
Java并发编程最佳实践:设计高性能的多线程系统
Java并发编程最佳实践:设计高性能的多线程系统
71 1
|
18天前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
38 4
|
2月前
|
监控 Java Linux
Java 性能调优:调整 GC 线程以获得最佳结果
Java 性能调优:调整 GC 线程以获得最佳结果
86 11
|
2月前
|
监控 安全 算法
线程死循环确实是多线程编程中的一个常见问题,它可能导致应用程序性能下降,甚至使整个系统变得不稳定。
线程死循环是多线程编程中常见的问题,可能导致性能下降或系统不稳定。通过代码审查、静态分析、日志监控、设置超时、使用锁机制、测试、选择线程安全的数据结构、限制线程数、使用现代并发库及培训,可有效预防和解决死循环问题。
73 1
|
2月前
|
监控 安全 算法
线程死循环是多线程编程中的常见问题,可能导致应用性能下降甚至系统不稳定。
【10月更文挑战第6天】线程死循环是多线程编程中的常见问题,可能导致应用性能下降甚至系统不稳定。为了解决这一问题,可以通过代码审查、静态分析、添加日志监控、设置超时机制、使用锁和同步机制、进行全面测试、选用线程安全的数据结构、限制线程数量、利用现代并发库,并对团队进行培训等方法来预防和减少死循环的发生。尽管如此,多线程编程的复杂性仍需要持续监控和维护以确保系统稳定。
65 3
|
4月前
|
监控 负载均衡 算法
线程数突增!领导说再这么写就GC掉我:深入理解与优化策略
【8月更文挑战第29天】在软件开发的世界里,性能优化总是开发者们绕不开的话题。特别是当面对“线程数突增”这样的紧急情况时,更是考验着我们的技术功底和问题解决能力。今天,我们就来深入探讨这一话题,分享一些工作学习中积累的技术干货,帮助大家避免被“GC”(垃圾回收,也常用来幽默地表示“被炒鱿鱼”)的尴尬。
51 2
|
3月前
|
并行计算 API 调度
探索Python中的并发编程:线程与进程的对比分析
【9月更文挑战第21天】本文深入探讨了Python中并发编程的核心概念,通过直观的代码示例和清晰的逻辑推理,引导读者理解线程与进程在解决并发问题时的不同应用场景。我们将从基础理论出发,逐步过渡到实际案例分析,旨在揭示Python并发模型的内在机制,并比较它们在执行效率、资源占用和适用场景方面的差异。文章不仅适合初学者构建并发编程的基础认识,同时也为有经验的开发者提供深度思考的视角。
|
3月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
33 0