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

原文链接: http://www.tianxiaohui.com/index.php/interestingbug.html

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 引起的 死循环问题

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

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

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

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

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

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

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