如何确诊由 HashMap 引起的 死循环问题

Java 开发者都知道 java.util.HashMap 是非线程安全的, 一旦涉及多线程情况使用同一个 Map, 除非做了其他同步, 否则都不应该使用 HashMap. 然而现实情况是: 时不时发现生产环境在多线程情况下使用了 HashMap, 导致死循环, 继而不再响应其他请求.

那么如何诊断并确认是这种情况呢?
首先, 我们从症状看起, 一般出现了这种情况, 1. CPU 使用率相对平时情况会高很多; 2. tomcat http 线程陷入 HashMap 死循环, 无法退出, 导致不能响应新的请求;

一旦发生上面症状, 就可以怀疑 HashMap 导致的死循环问题了. 要想确认是不是这个问题, 基本需要2步: 1. 查看 thread dump, 看是不是有线程一直在 HashMap.getEntry() 上面; 2. 查看 heap dump 里面是不是 HashMap 某个桶的链表出现了死循环.

  1. 查看 thread dump, 确认是不是有线程一直在 HashMap.getEntry() 上面
    使用 jstack 做 thread dump, 查看是不是有一个或多个线程最顶上的栈像下面这样:
    HashMap.png

上面的截图可以看到, 我这个 thread dump 里面有40个线程都卡在这一行. 因为我的 tomcat 就开了40个 http daemon 线程, 基本每个都卡在这里了.

  1. 查看 heap dump, 确认链表死循环
    首先, 使用 jcmd 或 jmap 或其它工具做一个 heap dump; 然后使用 MAT 或者其他 heap 分析工具找出 HashMap 里某个 bucket 里面的循环链表. 我这里以 MAT 举例, 截图来说明.

thread.png

如上图, 找到这个线程, 可以看到栈里面和我们 thread dump 里面的基本一样, 它卡在那个 getEntry() 方法还没出来. 点开它的 local variable, 能看到当前正在循环的 HashMap$Entry. 然后右键点击这个变量, 通过菜单 List Objects -> with outgoing references, 可以看到当前 Entry refer 的 next 对象:

next.png

从上面的图可以看到, 这2个 Entry 对象的 next 互相指向对方, 导致了死循环, 致使当前线程陷入之后, 无法退出.

通过上面的2步, 基本就确诊了是不是由 HashMap 引起的死循环问题.

标签: none

添加新评论