记一次线上故障--HashMap在多线程条件下运行造成CPU 100%

Posted 潇潇雨歇_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一次线上故障--HashMap在多线程条件下运行造成CPU 100%相关的知识,希望对你有一定的参考价值。

       Java程序关联的CPU打满100%一般都是程序编写不规范,引发的死循环造成。为什么HashMap的组装数据及调用会造成死循环呢,这里需要从HashMap的底层数据结构分析原因。

一.JDK1.8 HashMap的数据结构

数组 + 链表 + 红黑树

二.结合问题代码与HahsMap底层结构分析

2.1 线上的问题代码

public Map<String,Object> selectByName(List<String> nameList) 
        Map<String,Object> dataMap = new HashMap();
        if (CollUtil.isNotEmpty(dataList))
            dataList.parallelStream().forEach(item->
		         //业务处理
                if (StringUtils.isNotEmpty(TypeVO.getType()))
                    dataMap.put(item.getName(),TypeVO);
                
            );
        
        return dataMap;

2.2 HashMap的扩容底层实现如下

void transfer(Entry[] newTable) 
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) 
            Entry<K,V> e = src[j];
            if (e != null) 
                src[j] = null;
                do 
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                 while (e != null);    
            
        
 

问题就出现在了while (e != null),从上面的代码看来,每一个线程进来都先执行dataMap = new HashMap();这个时候dataMap是空的,所以在执行下面的操作的时候进入了某一个不可以随意更改状态的代码中,再加上高并发,一直被new HashMap(),while一直被执行,变成了死循环。cpu就瞬间飙升到100%,一直持续到请求数降低的时候。

三.解决办法

重构问题代码,多线程并发扩容时就会出现环形引用的问题,从而导致死循环的出现,一直死循环就会导致 CPU 运行 100%,所以在多线程使用时,我们需要使用 ConcurrentHashMap 来替代 HashMap,ConcurrentHashMap是线程安全的Map,适合在多线程环境下稳定运行。

public ConcurrentHashMap<String,Object> selectByName(List<String> nameList) 
        ConcurrentHashMap<String,Object> dataMap = new ConcurrentHashMap();
        if (CollUtil.isNotEmpty(dataList))
            dataList.parallelStream().forEach(item->
                //业务处理
                if (StringUtils.isNotEmpty(typeVO.getType()))
                    dataMap.put(item.getName(),typeVO);
                
            );
        
        return dataMap;
 

四.附录:定位到CPU打满100%问题代码的命令

4.1 查看Linux主机各进程CPU使用率情况

top -c

4.2 通过4.1查出cpu使用率最高的进程pid,查询该pid进程底下的线程使用情况

top -Hp <PID>

4.3 通过4.2查出cpu使用率最高的线程tid,为了便于在jstack中查询,先把线程tid转换成16进制,如1f529

printf "%x\\n" <TID>

4.4  通过jstack命令查看相应线程的堆栈

jstack <PID> | grep <16进制的TID> -A 30

4.5 整套流程命令示例

1.top -c  查出cpu使用率最高进程PID为127387
2.top -Hp 127387  查出线程使用率最高TID为128739
3.printf "%x\\n" 128739 查出TID的16进制为1f529
4.jstack 127387 | grep 1f529 -A 30   查出堆栈信息,问题代码

以上是关于记一次线上故障--HashMap在多线程条件下运行造成CPU 100%的主要内容,如果未能解决你的问题,请参考以下文章

记一次线上故障--HashMap在多线程条件下运行造成CPU 100%

记一次线上故障处理

记一次线上环境 ES 主分片为分配故障

系统上线那点事 - 记一次线上系统故障

记一次线上压测Dubbo线程池队列满的问题

记一次线上Java程序导致服务器CPU占用率过高的问题排除过程