记录一次由于OOM的unable to create new native thread异常引发的线上问题

早上刚到公司,产品就跑过来说:xxx看看这个工单数据是怎么回事?为什么和预期的不一致呢?顿时感觉头大。早饭也顾不上吃了,就拿到数据ID在Elk中搜索日志…


1.问题描述:

根据数据ID,在ELK海量日志中搜索,发现该请求竟然OOM了!!! what? 真是让我惊讶。 仔细查看下报错的原因竟是,JVM申请不到线程来处理本次请求,而导致的OOM。具体日志如下:

在这里插入图片描述


2.原因定位:

nested exception is java.lang.OutOfMemoryError: unable to create new native thread

字面意思:jvm无法创建新的线程来处理此次请求

分析导致次异常的原因主要由以下两点导致的:

1. 开启线程数过多,导致jvm申请不到线程
2. 服务器用户线程数设置太小(这种情况较少)
3. 堆内存及堆外空间内存不足


3.分析并解决

3.1开启线程数过多,导致jvm申请不到线程

部署系统时由于运维同学不采用我的建议(jvm添加oom异常时导出dump文件),导致无法分析dump文件,很难定位问题… 那也只能从其它层面分析这个问题了。

3.1.1 分析线程池使用

1.首先检查项目所有关于线程池使用的相关代码,发现了有好几处地方定义了线程池的使用,如下:

// 创建的线程数最大值为int类型的最大值。
ExecutorService executorService = Executors.newCachedThreadPool();

当我看到上述代码时,心想写这个代码的同学一定对线程池的不熟悉,只处于会用的状态…

根据这个代码结合我们具体的业务场景,线程池调用的接口是最频繁的,但是由于执行链过长,并发一集中,就导致系统创建了很多的线程,直至jvm无法申请线程…

于是就对线程池的使用做了一些调整,具体如下:

ThreadPoolExecutor executorService = new ThreadPoolExecutor(10, 20,
            60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            new DefaultThreadFactory("工单同步线程"),
            new RejectHandleBase());

/**
 * 自定义线程拒绝策略
 *
 */
@Slf4j
public class RejectHandleBase implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
        log.info("进入线程拒绝策略");
        BlockingQueue<Runnable> queue = executor.getQueue();
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.error("线程拒绝策略异常:{}", e);
        }
    }

}

3.2 分析SimpleAsyncTaskExecutor.java 237异常

除了上述一个线程的优化,还远远不够,继续分析错误的日志,又发现了下面的错误日志:
OOM异常信息2
根据异常信息,找到对应源码如下:

在这里插入图片描述
新线程

查看源码分析得出:发现每次请求都会调用doExecute(),会创建一个线程,这肯定是我们系统中哪个地方引起的。
于是查询类的关系图,排查哪些代码会调用doExecute(),具体如下:
在这里插入图片描述
由类图得出,SimpleAsyncTaskExecuto.javar实现了AsyncListenableTaskExecutor.java接口,之后查看相应源码猜测是由:@Async注解引起的线程数增加

为了印证上面的猜想,于是本地开启了调试,具体如下:
在这里插入图片描述

当看到 async 数据时,答案已经得出了,就是因为添加了 @Async注解引发的线程数增加

于是查找项目中用到 @Async注解 的地方。竟然发现 全局切面类 中调用了 @Async注解中类的某个方法 ,导致每次请求都会创建一个新线程。
具体代码如下:
在这里插入图片描述
具体实现:
在这里插入图片描述
线程增加的原因又排查出了一个,接着评估了上述代码的作用,与业务无法,用处不大,就删除了相关代码…


3.3 尝试增加程序线程数

另外:用户线程数如果小于了java程序所申请的线程数,可以尝试调大用户线程数…


3.4堆内存及堆外空间内存不足

分析具体的GC日志文件
服务器是6G内存,部署3个节点,jvm参数大概如下:

-Xmx1024m -Xms1024m -Xmn400m
题外话:我们服务器配置是真的低…

由于部署3个节点,内存占用为3G,剩余3G大小为系统其它线程和线程栈占用。 结合下面进程能够申请到的线程数计算结果得出,我们服务器配置是比较低

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

各个参数的解释如下:

MaxProcessMemory进程最大寻址空间 (32位,进程的虚拟空间为4G,但是大约2G可用于一般使用。64位限制实际会消失)
JVMMMEMORYjvm的内存空间(堆+永久区)-Xmx大小 (应该是实际分配大小)
ReservedOsMemory操作系统预留内存
ThreadStackSize-Xss大小

解决意见:加大服务器内存,保证堆外空间充足

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值