记一次生产环境OOM的排查
date
Dec 9, 2021
slug
记一次生产环境OOM的排查
status
Published
tags
Java
summary
公司有个项目,正常发版的过程中,有个自动化测试的流程,如果测试不通过,就会导致发版失败。为了测试方便,项目中留有内部访问的 REST API,仅供相关的测试代码调用。近期发版非常不稳定,经常报这些测试相关的 API
502 Bad Gateway
,并且同时可能出现 Canary 实例的 OOM 报警。type
Post
Background
公司有个项目,正常发版的过程中,有个自动化测试的流程,如果测试不通过,就会导致发版失败。为了测试方便,项目中留有内部访问的 REST API,仅供相关的测试代码调用。近期发版非常不稳定,经常报这些测试相关的 API
502 Bad Gateway
,并且同时可能出现 Canary 实例的 OOM 报警。Research
拿到 Heap Dump 的过程比较曲折,这里就不说了,主要是需要在发版之前留心,在问题出现之后立刻去进行 dump ,否则现场被销毁掉就无从下手了。主要说下拿到 dump 文件之后应该如何分析。
如果对项目情况不够了解,建议最好拿两个 dump,正常状态和 OOM 发生时的状态,这样有对比,可以更容易的发现问题。
可以看到,正常状态下,比较大的对象是
DefaultListableBeanFactory
和ParallelWebappClassLoader
异常状态下,最大的对象是个
ScheduledThreadPoolExecutor
线程池占用的资源得不到释放导致 OOM 是非常有可能的,所以我接下来详细看一下这个线程池里都有什么东西。
可以看到,线程池占用的内存,基本都被
DelayedWorkQueue
占用,这个Queue
中,包含若干个ScheduledFutureTask
,其中可以看到相关的项目代码(TestSubscriptionController
),也可以看到占内存最多的是Subscription
对象(也是项目代码)。由于出现了项目代码的线索,接下来我们可以从代码方面尝试分析问题了
测试逻辑中,会调用这个方法,一共有两个线程池
executorService
和scheduledExecutorService
,主要逻辑在executorService
中,sheduledExecutorService
是作为保底机制,释放worker
中占用的资源,避免影响其他的 test case。这里有两个问题,第一,
sheduledExecutorService
这个线程池是个SingleThreadScheduledExecutor
,所以其中所有逻辑是串行的;第二,schedule
执行的 delay 是比较久的,有6分钟。所以如果在测试中,有大量的 worker 被 submit,
scheduledExecutorService
内的任务又得不到执行,导致 worker 占用的对象不能被 GC,导致 OOM。这也解释了为什么 OOM 总会在相关 API 报502
之后出现,因为heap中可用内存越来越小,JVM 会不断的尝试 GC,但是因为绝大多数内存都无法 GC,JVM 没有资源去执行正常的业务逻辑,即使执行,也会不断的创建新的任务放到线程池中,导致问题更加严重。Solution
在跟相关同事确认之后,采用了其他方式进行清理工作,而不是采用线程池的方式。再次尝试发版,没有 OOM 的问题出现了。