一、背景

线上应用系统1300+多个线程,而且线程不会销毁,达到了1300个线程的UMP告警阈值;

容器情况:
JRE版本:1.8.0_20
CPU个数:4
web服务器:undertow 2.0.16.Final

二、分析

1、下载dump日志

jmap -dump:live,format=b,file=heap.hprof 进程号

2、 使用jvisualvm进行分析

image.png
image.png

分析发现1300多个线程大致分为如下几类:

  1. 守护线程400+ 个
  2. XNIO-1 I/O 线程96个,处于Running状态
  3. XNIO task 线程768,处于waiting状态
  4. 其他线程:几十个

通过查阅资料可知XNIO开头的都是undertow线程(undertow是基于xnio框架实现的)

XNIO-1 I/O 线程:是服务中的非阻塞线程,线程数默认等于CUP核数(其实是与2做比较取最大值)
XNIO task 线程:是阻塞线程,默认情况下该类线程对应的线程池的核心线程数和最大线程数是一致的,都是XNIO-1 I/O线程数的8倍;由于这些线程全是核心线程,所以这些线程都不会被销毁;

问题

我们的应用服务在部署的时候,容器一般都是4核或者8核,那为什么会创建这么多的96个非阻塞线程呢?

答案

应用服务使用的是docker容器创建的linux环境,在这个环境下,如果使用Java SE 8u131 (JRE 1.8.0_131-b11)以下的版本,通过Runtime.getRuntime().availableProcessors(),获取到的cpu核数是宿主机的核数,undertow就是这样获取的;

而我们的容器中JDK版本为1.8.0_20,就是上述版本之下,这样就能解释为什么默认情况下会创建XNIO-1 I/O 线程96个,XNIO task768线程个了;

附Jdk8版本号:https://www.java.com/zh-CN/download/help/release_changes.html

三、解决

方案1

在yml文件中指定undertow的线程数:

server.undertow.io-threads=4
server.undertow.worker-threads=40

方案2

使用Java SE 8u131 (JRE 1.8.0_131-b11)以上的版本,或者用JDK11;

四、思考

其实如果比较熟悉undertow,知道undertow中的线程的命名、以及线程数的关系的话,通过查看服务日志基本就能定位到问题
undertow配置的核心参数如下:

# 设置IO线程数, 它主要执行非阻塞的任务,它们会负责多个连接, 默认设置每个CPU核心一个线程,XNIO建议设置默认值即可
server.undertow.io-threads=4

# 阻塞任务线程池, 当执行类似servlet请求阻塞IO操作, undertow会从这个线程池中取得线程
# 它的值设置取决于系统线程执行任务的阻塞系数,默认值是IO线程数*8,一般情况下每个C大约10个线程即可
server.undertow.worker-threads=32

# 以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
# 每块buffer的空间大小,越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可
server.undertow.buffers-per-region=1024

# 是否分配的直接内存(NIO直接分配的堆外内存)
server.undertos-per-region
server.undertow.buffer-size=1024

# 每个区分配的buffer数量 , 所以pool的大小是buffer-size * bufferw.direct-buffers=true

参考:https://undertow.io/undertow-docs/undertow-docs-2.1.0/index.html

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐