Heap OutOfMemory - ScheduledFutureTask
Key
1 |
|
Overview
Dump 文件分析发现 java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask
的堆内存占用约 2 G,基本确认问题
Analysis
尝试通过查看类 Reference 关系,确定下发生内存内泄漏的原因
Outgoing Reference
先看下实例内部引用「Outgoing Reference」
可以看到有 20,000,000+ 的实例,我们应该看哪个呢?
通过 Retained Size 排序,找到 size 相同的出现频率最高的实例对象
这里有两个明显异常的地方
- 调度线程的调度周期是
Integer.MAX_VALUE
- 调度线程的队列长度非常大,约等于这个类实例的数量
- 调度线程的任务已经被取消
status=4
Incoming Reference
查看引用实例的地方「Incoming Reference」
按照 Retained Size
排序,找出同样大小,出现频率最高的对象实例
到这儿,我们基本可以确认是 HystrixTimer
相关的实现导致了 ScheduledThreadPollExecutor
的延迟队列(unlimit)中无限制的堆积了 ScheduledFutureTask
Why
通过关键字 HystrixTimer ScheduledFutureTask
Google。
通过搜索结果,基本可以定位 问题主要和 Hystrix Timeout 相关。
我们可以发现, 已经有人在 16 年遇到过类似的问题了
原因,上面的 Google Group 解释的比较清楚了。可以对照源码看一下。
Hystrix
每次会调用HystrixTimer
来生成一个TimeListener
TimeListener
会使用ScheduledThreadPoolExecutor.scheduleAtFixRate()
来申明一个调度队列,因为我们的Hystrix.Setter.executionTimeout
设置为了Integer.MAX_VALUE
, 导致调度队列在初始化时,调度周期 =Integer.MAX_VALUE
- Command 执行完成后,调度线程池的延迟队列中的调度任务状态会被设置为取消,但因为 JDK7 的默认策略是取消任务不会立即移除,只有在任务触发时才会移除,所以调度任务会常驻在队列中,直到任务触发
- 调度线程池中的延迟队列属于无界(unlimit)队列,所以队列中的任务会一直堆积,直到
JVM Restart
Solution
调整 Hystrix.Setter.executionTimeout
为合理数值 / 关闭 Hystrix Timeout
Ref
Incoming Reference、Outgoing Reference
ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(boolean)
Heap OutOfMemory - ScheduledFutureTask
http://kenneth-hao.github.io/2020/03/10/heap-out-of-memory-ScheduleFutureTask/