起因
收到线上报警。发现老年代满了,CGC直接回收不了。导致内存溢出。

image.png
环境
线上环境使用的java17,垃圾回收器使用的ZGC。
表象原因
从线上拉取之前保存的dump.hprof文件。导入mat(eclipse memory analyzer)中。
- 从图可以看出
ThreadPoolExecutor 370.4 MB
其他对象 约2.5 GB(饼图中2个浅色区块)
未分类内存 355.2 MB(深蓝色区块)

image.png
- 从报告来看
- 线程池泄露(Problem Suspect 1/3/4):
- 多个ThreadPoolExecutor实例总占用2.6GB+
- 特征:工作队列无限积压任务(截图3/4显示工作线程ws-exe-3/7关联泄露)
- WebSocket泄露(Problem Suspect 2):
- 278个WsFrameServer实例占用576MB
- 根源:Tomcat的WsServerContainer持有未释放的WebSocket连接
- 直接关联线程:http-nio-0.0.0.0-8228-exec-9

image.png

image.png

image.png

image.png
- 内存分析
主要泄漏对象
-
ThreadPoolExecutor实例(地址0x8001a2e5940):
- 通过workQueue字段持有LinkedBlockingQueue@0x8001a2e59a8,表明线程池任务队列积压。
-
SdwanWsSessionRegistry实例(地址0x80034c7ed28):
- 维护WebSocket会话(SdwanWsSession),但未正确清理失效会话。
- 多个工作线程(如ws-exe-4、ws-exe-5等):
- 持有SdwanWsSession或XtermWsServer对象,线程未释放导致关联对象无法回收。
典型泄漏对象
- 持有SdwanWsSession或XtermWsServer对象,线程未释放导致关联对象无法回收。
- SdwanWsSession(WebSocket会话)
- ThreadPoolExecutor(线程池)
-
ConcurrentHashMap(用于存储会话的注册表)
image.png
总结: 从报告上来看,是工作线程消息挤压。然后线程池又是无界队列。消息堆积导致了OOM。
深层原因
上面我们分析到了消息堆积。下面我们继续查找是什么原因导致的。从线程概况列表看出。主要问题出在了ws-exe-3和ws-exe-6两个线程上。

image.png
ws-exe-3
我们点开ws-exe-3详情页。可以看到该线程是出于无限期等锁的状态[alive, parked, waiting, waiting indefinitely]。

image.png
该线程持有了XtermWsSessionRegistry的写锁(remove方法)。想要去拿取SdwanWsSessionRegistry 中的读锁。处于等待读锁中。

image.png
ws-exe-6
该线程也是处于无线等锁的状态。

image.png
从详情看出。该线程持有SdwanWsSessionRegistry的写锁。在尝试去拿XtermWsSessionRegistry的读锁。处于等锁状态中。

image.png
结论
从上面分析来看。ws-exe-3和ws-exe-6形成了死锁循环链。导致消息堆积。
