hashmap死循环示例及检测方法
发布日期:2021-05-06 20:09:41 浏览次数:27 分类:技术文章

本文共 1806 字,大约阅读时间需要 6 分钟。

以前的游戏项目经常使用map作为缓存容器,但由于有些开发人员并发处理意识不强,在多线程环境上误用了线程不安全的HashMap对象,导致死循环发生。一旦发生了死循环,cpu就会暴涨,非重启服务不能解决(=。 =)

众所周知,jdk提供的HashMap类属于线程不安全容器,只能在单线程环境使用,如果在多线程环境下使用可能造成数据丢失,rehash加倍扩容,最严重还会导致死循环。所以在并发环境一定要使用安全容器ConcurrentHashMap。

死循环是怎么发生的呢?简单的说,就是落在同一个hash冲突链的两个元素在多线程扩容情况下出现了环形节点(JDK8及以后的版本,依然是线程不安全,但不会发生死循环)。具体的原因分析可参考()

下面先演示一段hashmap死循环的例子,然后说明如何使用jdk提供的工具——jstack,来定位出现问题的代码

一、死循环代码示例(JDK1.7环境)

package demo;import java.util.HashMap;public class HashMapTest{  	private HashMap
map = new HashMap<>(); public HashMapTest(){ Thread t1 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i), Integer.valueOf(i)); } } }; Thread t2 = new Thread() { public void run() { for (int i = 0; i < 50000; i++) { map.put(new Integer(i),Integer.valueOf(i)); } } }; t1.start(); t2.start(); } public static void main(String[] args) { new HashMapTest(); } }

死循环的概率还是非常低的,比较难以重现。为了提高出现概率,采用多次迭代测试。

写个简单的shell脚本(run.sh),循环次数写大点,如下所示:

#!/bin/bashfor i in `seq 1 10000`; do      echo $i    java demo.HashMapTestdone    exit 0

脚本唯一的作用就是多次运行java代码。为了清楚感知程序已经发生了死循环,在每次迭代中输出当前的迭代次数。一旦迭代输出停止了,就说明死循环出现了。

在控制台执行脚本,命令为"./run.sh",然后上个厕所,喝杯茶……

我运气还是蛮好的,回来的时候已经发生了死循环,如下图(卡住在第55次迭代)

 

二、jstack定位问题

怀疑程序出现死循环后,我们可以用一种不是很严谨的手段来检查。

由于死循环一般是我们的业务代码引起的。当程序发生了死循环,那么线程堆栈肯定有某一条线程卡在我们的业务代码上。假设我们的项目的包路径前缀是com.kingston.xx,我们就可以通过jstack检查该关键字。使用命令 “jstack -pid|grep com.kingston.xx”,如果多次执行(间隔3秒左右)都命中同一段代码,那么我们可以高度怀疑对应的逻辑出现死循环。

当然,最准确最严谨的手段还是按照下面的流程:

1.查找jvm进程id (pid=23199)

ps -ef| grep java

2.查找发生死循环的线程id(threadId=23214),cpu利用率暴表的第一条记录就是了。将十进制23214转为十六进制5aae

 

top -Hp 23199

3.使用jstack导出thread dump信息

jstack -l 23199 > ~/threaddump.txt

4.分析dump数据,查找cpu最高的线程的运行堆栈。可以看出,死循环发生在hashmap的put()方法。

cat threaddump.txt |grep -A10 5aae

 

 

 

上一篇:游戏服务端开发之基础概念扫盲篇
下一篇:游戏服务端之查看生产环境的内存数据

发表评论

最新留言

关注你微信了!
[***.104.42.241]2025年04月01日 03时30分30秒