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


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

    一.JDK1.8 HashMap的数据结构

    数组 + 链表 + 红黑树

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

    2.1 线上的问题代码

    1. public Map<String,Object> selectByName(List<String> nameList) {
    2. Map<String,Object> dataMap = new HashMap();
    3. if (CollUtil.isNotEmpty(dataList)){
    4. dataList.parallelStream().forEach(item->{
    5. //业务处理
    6. if (StringUtils.isNotEmpty(TypeVO.getType())){
    7. dataMap.put(item.getName(),TypeVO);
    8. }
    9. });
    10. }
    11. return dataMap;
    12. }

    2.2 HashMap的扩容底层实现如下

    1. void transfer(Entry[] newTable) {
    2. Entry[] src = table;
    3. int newCapacity = newTable.length;
    4. for (int j = 0; j < src.length; j++) {
    5. Entry<K,V> e = src[j];
    6. if (e != null) {
    7. src[j] = null;
    8. do {
    9. Entry<K,V> next = e.next;
    10. int i = indexFor(e.hash, newCapacity);
    11. e.next = newTable[i];
    12. newTable[i] = e;
    13. e = next;
    14. } while (e != null);
    15. }
    16. }
    17. }

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

    三.解决办法

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

    1. public ConcurrentHashMap<String,Object> selectByName(List<String> nameList) {
    2. ConcurrentHashMap<String,Object> dataMap = new ConcurrentHashMap();
    3. if (CollUtil.isNotEmpty(dataList)){
    4. dataList.parallelStream().forEach(item->{
    5. //业务处理
    6. if (StringUtils.isNotEmpty(typeVO.getType())){
    7. dataMap.put(item.getName(),typeVO);
    8. }
    9. });
    10. }
    11. return dataMap;
    12. }

    四.附录:定位到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. 1.top -c 查出cpu使用率最高进程PID为127387
    2. 2.top -Hp 127387 查出线程使用率最高TID为128739
    3. 3.printf "%x\n" 128739 查出TID的16进制为1f529
    4. 4.jstack 127387 | grep 1f529 -A 30 查出堆栈信息,问题代码

  • 相关阅读:
    微电网两阶段鲁棒优化问题(Matlab代码实现)
    Java内存模型:创建对象在堆区如何分配内存
    Django(三、数据的增删改查、Django生命周期流程图)
    使用 Meltano 将数据从 Snowflake 导入到 Elasticsearch:开发者之旅
    什么是腾讯云主机安全,主要有哪些功能作用?
    React 中 react-i18next 切换语言( 项目国际化 )
    Qt使用FFmpeg的动态库
    解释器模式——化繁为简的翻译机
    win10安全中心打开是空白的怎么解决
    手把手教你做智能合约开源|多文件合约开源|引用文件开源
  • 原文地址:https://blog.csdn.net/linhaiyun_ytdx/article/details/125427134