1.面试官问:SpringBoot中@Async默认线程池导致OOM如何解决?
2.JobIntentService源码解析
面试官问:SpringBoot中@Async默认线程池导致OOM如何解决?
面临问题
在进行项目测试时,码解我们遇到了内存溢出(OOM)问题,码解具体日志显示为“unable to create new native thread”。码解该项目使用SpringBoot架构,码解我们通过Jstack工具分析,码解发现大量线程处于可运行状态,码解拐点战法指标源码表明线程创建量过大。码解
问题解析
内存溢出主要有三种类型:第一,码解PermGen space,码解通常因程序中加载大量JAR或Class文件引起;第二,码解Java heap space,码解由程序创建对象过多导致;第三,码解无法创建新本地线程,码解这可能因创建线程数量过多,码解导致内存消耗过大。码解
初步分析
根据日志分析,我们怀疑是线程创建过多导致的问题。通过将应用线程信息打印到文件,发现大量线程处于可运行状态,这进一步确认了线程创建过多的假设。
代码探究
问题出在日志写库服务的substring 源码`writeLog`方法上,该方法使用了`@Async`注解,实现异步写入逻辑。但服务并未自定义异步配置,而是使用了Spring默认的`SimpleAsyncTaskExecutor`线程池,该配置默认为每个任务创建一个新线程,这在高并发压测环境下,可能导致线程数量激增,从而导致内存溢出。
深入了解
`SimpleAsyncTaskExecutor`线程池具备限流机制,通过`concurrencyLimit`属性控制。mvvm源码默认情况下,此属性值为-1,即不开启限流,导致在高并发情况下持续创建线程。源码分析揭示了限流机制实现,即当线程数超过`concurrencyLimit`时,当前线程会进入等待状态,直至线程数减至允许范围。
实践验证
我们通过模拟测试验证了此机制。在未开启限流的asterisknow源码情况下,启动个线程调用异步方法,使用Java VisualVM监控工具观察到线程数迅速增长,直至达到系统限制。开启限流后,线程数得到了有效控制,但这也引入了执行效率的降低,出现了主线程等待和线程竞争的情况。这种限流机制适用于任务处理速度较快的场景,对于处理时间较长的应用并不适用。
最终解决方案
为解决线程创建过多导致的源码替换内存溢出问题,我们采取了以下策略:
1. 自定义线程池,利用`LinkedBlockingQueue`阻塞队列限制线程池的最大线程数。
2. 定义拒绝策略,当任务队列满时,拒绝处理新任务并记录日志。
通过这些改进,我们成功解决了内存溢出问题,同时确保了应用的稳定性和响应效率。
JobIntentService源码解析
Android 8.0引入了更严格的系统资源管控,包括后台限制规则。
在Android 8.0中,禁止应用在后台运行时创建Service。
若应用在后台运行,将会收到错误提示。
JobIntentService是Android 8.0中新增的类,继承自Service。
该类用于执行加入队列的任务。对于Android 8.0及以上系统,JobIntentService任务将通过JobScheduler.enqueue执行,而8.0以下系统则继续使用Context.startService。
JobIntentService使用便捷,只需调用YourService.enqueueWork(context, new Intent())方法。
相较于JobService,JobIntentService简化了操作,开发者无需关注其生命周期,避免了在后台运行时创建Service导致的crash问题,且通过静态方法即可启动。
源码解析如下:首先记录几个关键变量的含义。
在Android 8.0以上的系统中,执行流程如下。
work的具体逻辑处理在何处?
通过JobService的工作原理,查找onStartJob方法。
最终,处理work的逻辑会流转至AsyncTask中,通过protected abstract void onHandleWork(@NonNull Intent intent)方法实现。
子类需实现jobIntentService处理work,使用线程池的AsyncTask执行,无需考虑主线程阻塞问题。
针对Android 8.0以下系统,流程如下:回到onStartCommand方法。
同样,最终会流转至Asynctask任务执行onHandleWork。