线程池我用了一年都没出过问题,直到队列堆了10万个任务OOM打挂服务,查了三天才发现是这个参数惹的祸

线程池也会导致OOM的原因 我这边从一个问题引出这次的话题,我们可能会在开中碰到一种OOM问题,java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again相信很多人碰到过这个错误,很容易从网上搜索到出现这个问题的原因是线程过多,那线程过多为什么会导致OOM?线程什么情况下会释放资源?你又能如何做到让它不释放资源?有的人可能会想到,那既然创建线程过多会导致OOM,那我用线程池不就行了。但是有没有想过,线程池,也可能会造成OOM。 阅读详情

线程池这玩意,我用了一年多,一直觉得它是最稳的东西。

corePoolSize 设个 10,maximumPoolSize 设个 20,队列用 LinkedBlockingQueue,拒绝策略用 CallerRunsPolicy。每次面试被问到线程池参数,我都能倒背如流。同事问我线程池怎么配,我也能张嘴就来。

直到有一天凌晨两点,我被电话叫醒。

「服务挂了,OOM了,你赶紧看看。」

我打开电脑一看,堆内存直接打满,GC 日志刷了几十个 G。再一看线程池监控,队列堆积了 10 万个任务。

10 万个啊。。。

我当时的第一反应是,不对啊,我用的是 LinkedBlockingQueue,它默认容量是 Integer.MAX_VALUE,理论上确实可以无限堆。但问题是,我从来没想过它真的会堆到这个量级。

查了三天,把 GC 日志翻烂了,把堆 dump 文件拉下来用 MAT 分析了两遍,终于找到了根因。

问题不在线程池本身,而在一个我从来没注意过的参数。

说起来你可能不信,这个参数叫「队列容量」。就是那个我从来没显式设置过的值。

LinkedBlockingQueue 的无参构造函数,默认容量是 Integer.MAX_VALUE,也就是 21 亿多。你没看错,21 亿。这意味着什么?意味着你的线程池在高峰期如果处理速度跟不上提交速度,任务就会无限堆积,直到把内存吃光。

而我那段时间刚好在做一个批量导入的功能,用户上传一个 Excel,解析之后每一行数据都要丢进线程池去做校验和入库。一个 Excel 几万行,几个用户同时上传,队列就这么被撑爆了。

更要命的是,每个任务对象里面还持有一个数据库连接和一份解析后的数据模型,每个对象大概占几 KB。10 万个任务堆在队列里,光是对象本身就吃掉了几百兆内存。再加上每个任务等待期间持有的数据库连接,连接池也被耗尽了。

整个服务就像一个人被堵住了喉咙,吞不下去也吐不出来。

后来怎么修的呢?两个改动。

第一个,把 LinkedBlockingQueue 换成了 ArrayBlockingQueue,容量设成 1000。ArrayBlockingQueue 是基于数组实现的,它在创建的时候就分配好了内存,不会像 LinkedBlockingQueue 那样每个节点都要 new 一个 Node 对象。而且设了上限之后,超过 1000 的任务会直接走拒绝策略,而不是无限堆积。

第二个,把拒绝策略从 CallerRunsPolicy 换成了自定义的。CallerRunsPolicy 的意思是,如果线程池满了,就让提交任务的线程自己去执行。听着挺合理的对吧?但问题是,如果你的提交线程是 Tomcat 的工作线程,它被拿去执行任务了,谁来处理 HTTP 请求?

所以我的自定义拒绝策略是这样的,先尝试把任务存到一个本地的有界队列里,如果本地队列也满了,就返回一个友好的错误提示给用户「系统繁忙,请稍后再试」。最起码不会把整个服务拖死。

这块其实还有一个细节,很多人不知道。ThreadPoolExecutor 的 execute 方法,它的执行逻辑是这样的,先看核心线程数够不够,不够就创建新线程;核心线程满了就丢队列;队列满了就看能不能创建非核心线程;非核心线程也满了才走拒绝策略。

注意这个顺序,任务是先入队,后创建非核心线程。这意味着什么?意味着如果你的队列容量是 21 亿,那非核心线程永远不会被创建出来。maximumPoolSize 设得再大也没用,它就是个摆设。

我之前一直以为 maximumPoolSize 设成 20 就是最多 20 个线程在干活,实际上根本不是那回事。只有在队列满了之后,才会去创建超出核心线程数的线程。你用无界的 LinkedBlockingQueue,maximumPoolSize 就是个数字,没有实际意义。

这个坑踩过一次就记住了。

后来我又去翻了一下 JDK 的源码,在 ThreadPoolExecutor 的 execute 方法里看到了这段逻辑。英文注释写得很清楚,但我之前从来没认真读过。就像你天天用一把刀,但从来没看过刀刃是怎么开的一样。

说到源码,我突然想起一件事。ThreadPoolExecutor 有一个 allowCoreThreadTimeOut 参数,默认是 false。如果你设成 true,核心线程在空闲一段时间后也会被回收。很多人不知道这个参数的存在,觉得核心线程就是常驻的,永远不销毁。其实不是,你可以让它跟非核心线程一样,空闲就死。

但这个参数要慎用。我有一次在项目里把它设成了 true,结果低峰期的时候核心线程全部被回收了,高峰期来了请求又要重新创建线程,白白多了好几毫秒的延迟。对于延迟敏感的接口来说,这几毫秒可能就是压死骆驼的最后一根稻草。

线程池这东西,看着简单,其实里面的水很深。四个参数,corePoolSize、maximumPoolSize、keepAliveTime、workQueue,再加上拒绝策略,排列组合起来能玩出花来。但大多数人(包括之前的我)都是从网上抄一个配置,从来没认真想过为什么要这么配。

我自己的经验是,队列一定要有界,一定要有界,一定要有界。无界队列就是一颗定时炸弹,你不知道它什么时候会爆。宁可让任务被拒绝,也不能让它无限堆积。被拒绝了顶多是用户体验差一点,但 OOM 了整个服务都完蛋。

还有一个建议,线程池一定要加监控。我后来在项目里加了 ScheduledExecutorService,每隔 30 秒打印一次线程池的核心指标,活跃线程数、队列大小、已完成任务数。出了问题第一时间能看到,不用像我那样翻三天日志。

赫拉克利特说过,人不能两次踏进同一条河流。但程序员可以,只要你没有把坑记下来。我后来养成了一个习惯,每次踩完坑都写一个文档,记录问题现象、根因分析、解决方案。不是为了教别人,是为了防止未来的自己再犯同样的错。

凌晨两点的电话,我再也不想接了。

新手必看:瑞萨MCU选型时,QFP、QFN、BGA这些封装到底该怎么选?(附选型避坑指南) 本文详细解析了瑞萨MCU选型中QFP、QFN、BGA等封装的特点与适用场景,提供实战避坑指南。通过对比封装的空间占用、散热能力、焊接难度等核心维度,帮助工程师根据具体需求选择最合适的芯片封装,避免因选型不当导致的高成本与生产问题。 阅读详情

相关推荐

电力电子技术复习笔记

通常在开关过程前后加入谐振,使开关开通前电压先降到0,关断前电流先降到0,消除开关过程电压、电流的重叠,降低它们的变化率,从而大大减小甚至消除开关损耗。小信号线性化模型的原理:忽略开关频率分量和开关频率谐波分量及其边频分量,建立占空比、输入电压等的低频扰动对变换器中的电压、电流影响的模型。解:可以利用变压器的变比改变输出电压大小,避免控制过程的占空比过小或过大,提高控制精度,同时有利于减轻输出电路中电力电子器件的耐压要求。驱动电路:用于控制功率半导体器件的开关,从而控制电路的电流和电压。

Miracle Fan的博客 1万+

Executors创建线程池会造成OOM问题

看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因 写在前面 首先感谢大家阅读本篇文章,通过阅读本篇文章你将了解到: 线程池的定义 Executors创建线程池的几种方式 ThreadPoolExecutor对象 线程池执行任务逻辑和线程池参数的关系...

疯狂的蜗牛 9693

玩转肺癌目标检测数据集Lung-PET-CT-Dx ——①从TCIA获取影像数据集

该目标检测公共数据集源自 The Cancer Imaging Archive(TCIA),是肿瘤研究方面的著名的医学影像公开数据库。 数据以 DICOM 格式存储,由肺癌受试者的 CT 和 PET-CT DICOM 图像组成。

takedachia的博客 1万+

线程池中的阻塞队列选择

这是一个十分严重的问题 自从最近的某年某月某天起,线上服务开始变得不那么稳定。在高峰期,时常有几台机器的内存持续飙升,并且无法回收,导致服务不可用。 例如GC时间采样曲线: 和内存使用曲线: 图中所示,18:50-19:00的阶段,已经处于服务不可用的状态了。上游服务的超时异常会增加,该台机器会触发熔断。熔断触发后,改台机器的流量会打到其他机器,其他机器发生类似的情况的可能性会提高...

qq_35909080的博客 2万+

嵌入式开发必看:STM32 DMA双缓冲配置全攻略(含CubeMX教程)

本文详细解析了STM32 DMA双缓冲机制的原理与优势,并提供了基于CubeMX的完整配置教程。通过硬件自动切换两个缓冲区,实现DMA传输与CPU处理的真正并行,有效降低中断频率,提升高速数据采集、音频流处理等场景下的系统效率和实时性。

weixin_29212429的博客 174

TP4056在单节锂电池充电管理中的创新应用与电路优化

本文深入探讨了TP4056线性充电管理芯片在单节锂电池充电电路中的创新应用与优化方案。针对“边充边用”的核心挑战,文章详细解析了利用PMOS管构建智能供电切换电路的原理,实现了外部电源与电池供电的无缝、安全切换,有效杜绝了电池“边充边放”的风险。同时,提供了关键的元器件选型、PCB布局布线技巧及常见问题排查指南,为创客和硬件开发者提供了高性价比、高可靠性的锂电池电源管理实践方案。

rock5的博客 501

Java线程池深度解析(二):线程池类型与任务队列选择指南

在线程池第一篇章的学习中,我们复习了线程池的基本参数和使用以及优雅关停,由于篇幅原因还是有很多细节没有展开。现在提出几个问题来开始本次线程池的学习之旅:1.一共有几种线程池,区别是什么2.关于线程池的workQueue有哪些,怎么选择?CPU密集型• 线程数 = CPU核心数 + 1(容量适度)I/O密集型• 线程数 = 2 × CPU核心数(有界)或混合型任务• 拆分为CPU密集型队列 + I/O密集型队列• 使用区分优先级通用原则•永远为队列设置合理上限•。

weixin_42347876的博客 2610

BEV感知技术全景解析:从纯视觉到多传感器融合的实战指南

本文全面解析了自动驾驶中的BEV感知技术,从纯视觉到多传感器融合的实战应用。文章深入探讨了BEV如何提供“上帝视角”,对比了IPM、Lift-Splat-Shoot及Transformer等核心实现范式,并详细阐述了多传感器融合(尤其是中融合)的工程实践与优势,为开发者提供了从理论到原型搭建的完整指南。

win55的博客 1141

线程池参数配错致 OOM 复现:无界队列才是真凶(附完整代码)

回到这次 OOM:线程池用了无界队列,50000 个突发任务全部堆进队列,maxPoolSize 和拒绝策略根本没机会生效,最终堆内存撑爆。队列一定要有界不传容量就是无界,maxPoolSize 和拒绝策略全废了corePoolSize 不是越大越好,每个线程默认 1MB 栈空间,IO 密集型用 CPU×2 够了突发批量任务别直接砸线程池,用 MQ 削峰或 Semaphore 限流,从源头控制配置公式记一个就够了:CPU 密集型:N+1(N = CPU 核数)IO 密集型:2N。

qq_38669579的博客 504

c#算法(5)—最小二乘法(二次曲线拟合)

最小二乘法——二次曲线拟合

qq_34059233的博客 1659

YOLO26优化:特征融合 | 一种新颖的多尺度特征融合iAFF,适配小目标检测

在YOLO26中如何使用:iAFF加入Neck替代Concat;

①答疑群聊服务;②YOLO大模型知识问答系统;③计算机视觉论文生成智能体; 272

java线程池?,线程池有哪些创建方式?,线程池参数?,线程池的队列有哪些?,线程池的拒绝策略?,线程池的执行流程或原理?为什么禁止用Executor来创建线程池?

都知道在java中有线程,但是创建一个线程需要上下文切换,线程其实是非常消耗性能的,线程池能帮助开发者创建线程,提高开发效率,能控制线程的数量。:通过线程池,可以避免频繁地创建和销毁线程,从而减少了线程创建和上下文切换的开销,提高了系统的性能和效率。:线程池可以限制同时执行的线程数量,通过设置线程池的大小和队列容量,可以控制系统的并发度,防止因过多的线程导致系统资源耗尽或性能下降。:线程池可以统一管理和调度线程的生命周期,包括创建、执行和销毁线程等操作。

pangHu很菜 2万+

【ROS】ROS2导航Nav2:简介、安装、测试效果、错误处理

在ROS2中自动导航使用Nav2来实现。Nav2 使用几个独立的模块化服务,通过 ROS 2接口(例如动作服务器或服务)与行为树 (BT) 通信。Nav2 输入包括:TF转换、一个地图源、一个行为树 (BT) XML 文件和相关的传感器数据源;Nav2 输出速度命令;Nav2 支持多种机器人,比如:四足机器人、履带机器人、轮式机器人等。

郭老二 5089

线程池参数及配置

线程池-线程池参数及配置 在实际项目中线程的应用都会使用线程池来管理,线程池的常用参数及配置学习记录。 目录 线程池-线程池参数及配置 一、线程池 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。 如果并发的线程数多,并且每个线程都是...

xinpz的博客 1万+

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

服务重启了,如何保证线程池中的数据不丢失?

本文介绍了线程池的作用、原理及常见问题。线程池通过复用线程降低资源消耗,提高响应速度和管理性。线程池工作流程包括任务提交、处理和扩展回收。但使用不当可能导致OOM或数据丢失问题。为防止服务重启时线程池数据丢失,建议将任务数据持久化到数据库,状态标记为"待执行"。通过定时任务查询待执行任务并提交线程池处理,处理成功后更新状态。业务逻辑需做幂等设计,处理失败可自动重试,超过限制次数则标记为失败状态供人工处理。这种机制确保了服务重启时数据不丢失。

IT枫斗者的博客 1707

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Java 线程池等待队列问题

1、首先看下Executor获取线程池,这样方式,可以设置线程池的大小,但是了解线程池的内部原理的情况下,这样的线程池可能会引起OOM,原因在于 该线程池的等待队列最大长度默认为int的最大值,随口默写出来就是2147483647(2^31 -1,高中物理老师说过一句话,记住一些固定的数字可以预判一些问题)。线程池在提交任务时,如果线程池未达到最大线程数,则起线程执行任务,在达到最大值后,会放...

qq_22351805的博客 6864

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

线程池各个参数详解以及如何自定义线程池

一:为什么要自己定义线程池 虽然jdk提供了几种常用特性的线程池给我们,但是很多时候,我还是需要自己去自定义自己需要特征的线程池。并且阿里巴巴规范手册里面,就是不建议使用jdk的线程池,而是建议程序员手动创建线程池。 为什么呢?原文如下 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规...

hello world 2万+

线程池核心参数设置,如何避免任务队列引发的OOM?

掌握线程池核心参数设置技巧,有效防止因线程池的任务队列积压导致的OOM问题。适用于高并发场景,通过合理配置队列容量与拒绝策略提升系统稳定性,避免内存溢出风险,值得收藏。

FastDebug的博客 421

6万条数据,6万个任务,queueCapacity(队列容量)可以设置为10万吗

6万条数据,6万个任务,queueCapacity(队列容量)可以设置为10万吗

道友 494

线程池创建及如何避免OOM

本文给大家分析了Excutors导致OOM出现的原因,也为大家介绍了使用ThreadPoolExcutor手动创建线程池时每个参数的具体含义。

qq_41920323的博客 1106

线程池中阻塞队列有那些,该如何使用

本文介绍了阻塞队列和线程池的使用。阻塞队列在多线程中实现了高效的数据传输,支持自动阻塞和唤醒机制。文章详细讲解了BlockingQueue的核心方法,包括插入、移除和检查操作。在线程池部分,阐述了核心参数配置、工作流程及常见线程池类型(如FixedThreadPool、CachedThreadPool等)。最后强调应避免使用原生Executors创建线程池,以防止OOM风险,推荐自定义ThreadPoolExecutor,合理设置队列容量和拒绝策略。通过合理应用这些并发工具,可以构建高质量的多线程程序。

qq_52445645的博客 1105

Java-五种线程池,四种拒绝策略,三类阻塞队列

Java-五种线程池,四种拒绝策略,三种阻塞队列 三种阻塞队列: BlockingQueue<Runnable> workQueue = null; workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界 workQueue = new LinkedBlockingQueue<>...

草青工作室 的专栏 2万+

线程池核心参数介绍

说明:手工创建了一个线程池,核心线程数10,最大线程数20,任务队列100,使用默认的线程工厂生成线程,拒绝策略选择AbortPolicy。核心线程数(参数传递),最大线程数Integer.MAX_VALUE,任务队列采用延迟队列DelayedWorkQueue。核心线程数0,最大线程数Integer.MAX_VALUE,任务队列采用同步队列SynchronousQueue。SynchronousQueue:同步队列,即任务到来时,不放入队列等待,直接交给工作线程执行任务。

xujinwei_gingko的专栏 1181

Java线程池的任务消息队列

并发编程 队列

轩1024的博客 2618

自动挡线程池OOM最佳实践

配置完成后可启动SpringBoot项目调用接口等待OOM,待项目发出OOM错误后,可以去配置的路径下查找dump文件,一般以hprof为后缀,打开IDEA中的Profiler,将文件拖入Profiler即可,需要注意的是不同版本IDEA操作可能略有不同,可自行百度操作方式也可参考IDEA官网给出的操作教程。打开dump文件后可以看到程序在出现OOM错误时的资源占用情况,我们分配的最大堆内存是512MB,分析dump文件可以发现。占用了516MB的内存,并且频繁触发GC Root,我们查看。

FirstMrRight的博客 687

使用线程池的10个坑

大家好,我是捡田螺的小男孩。日常开发中,为了更好管理线程资源,减少创建线程和销毁线程的资源损耗,我们会使用线程池来执行一些异步任务。但是线程池使用不当,就可能会引发生产事故。今天田螺哥跟大家聊聊线程池的10个坑。大家看完肯定会有帮助的~线程池默认使用无界队列,任务过多导致OOM线程创建过多,导致OOM共享线程池,次要逻辑拖垮主要逻辑线程池拒绝策略的坑Spring内部线程池的坑使用线程池时,没有自定义命名线程池参数设置不合理线程池异常处理的坑使用完线程池忘记关闭。...

weiwenhou的博客 6564
上一篇: 工作8年没涨工资,面试外企给涨了50%。为了给领导面子,我说内部涨薪30%就留下,结果领导说:就你这水平,涨15%都多
下一篇: 凌晨两点被叫醒,我用AI排查了一个半小时没找到的bug
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值