JDK1.8 ConcurrentHashMap的死锁问题分析

1. 问题描述:

短信平台压测过程中遇到,频繁FullGC问题, CPU使用率达到90%,fullGC基本上5s中一次, 压测结束后一直持续。

截图.png

截图 (2).png

2. 原因分析:

2.1. 获取进程的堆栈日志

jstack pid > jstack.pid.log

2.2. 利用gceasy网站进行堆栈分析

官网地址: gceasy.io/ image.png 我们可以看到一共有419个线程,大部分处于wating状态, 有8个处于阻塞的状态。

堆栈分析

image.png 有8个线程基本上处于阻塞的状态,看下线程,主要mq是1mq的生产者线程和7个消费者线程处于阻塞状态,看下堆栈日志

image.png

image.png

基本上可以确认是CurrentHashmap的用法不当导致了死锁问题。

看下网站机器学习对问题原因的分析

easy_gc_analyze.png

2.4 根本原因分析

image.png 看下出现问题的代码, 上面的意思是,‎在计算过程中,其他线程尝试对Map执行的某些更新操作可能会被阻止,因此计算应简短而简单,并且不得尝试更新此映射的任何其他映射。

huiyu_bug_code.png

ConcurrentHashMap 源码分析

问题复现

public static void main(String[] args) {
    Map<String, Integer> map = new ConcurrentHashMap<>(16);
    System.out.println("AaAa".hashCode());
    System.out.println("BBBB".hashCode());
    map.computeIfAbsent(
            "AaAa",
            key -> {
                return map.computeIfAbsent(
                        "BBBB",
                        key2 -> 42);
            }
    );
}
复制代码

执行上面的代码会导致死锁,当key="AaAa" 不存在的时候,插入key="BBBB"的数据。但是两个key的haseCode 值一样导致的死锁。 image.png

ConcurrentHashMap#computeIfAbsent 源码

public V compute(K key,
                 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (key == null || remappingFunction == null)
        throw new NullPointerException();
    int h = spread(key.hashCode());
    V val = null;
    int delta = 0;
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
            Node<K,V> r = new ReservationNode<K,V>();
            synchronized (r) {
                if (casTabAt(tab, i, null, r)) {
                    binCount = 1;
                    Node<K,V> node = null;
                    try {
                        if ((val = remappingFunction.apply(key, null)) != null) {
                            delta = 1;
                            node = new Node<K,V>(h, key, val, null);
                        }
                    } finally {
                        setTabAt(tab, i, node);
                    }
                }
            }
            if (binCount != 0)
                break;
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            // 第1868行代码
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f, pred = null;; ++binCount) {
                            K ek;
                            if (e.hash == h &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                val = remappingFunction.apply(key, e.val);
                                if (val != null)
                                    e.val = val;
                                else {
                                    delta = -1;
                                    Node<K,V> en = e.next;
                                    if (pred != null)
                                        pred.next = en;
                                    else
                                        setTabAt(tab, i, en);
                                }
                                break;
                            }
                            pred = e;
                            if ((e = e.next) == null) {
                                val = remappingFunction.apply(key, null);
                                if (val != null) {
                                    delta = 1;
                                    pred.next =
                                        new Node<K,V>(h, key, val, null);
                                }
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        binCount = 1;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> r, p;
                        if ((r = t.root) != null)
                            p = r.findTreeNode(h, key, null);
                        else
                            p = null;
                        V pv = (p == null) ? null : p.val;
                        val = remappingFunction.apply(key, pv);
                        if (val != null) {
                            if (p != null)
                                p.val = val;
                            else {
                                delta = 1;
                                t.putTreeVal(h, key, val);
                            }
                        }
                        else if (p != null) {
                            delta = -1;
                            if (t.removeTreeNode(p))
                                setTabAt(tab, i, untreeify(t.first));
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                break;
            }
        }
    }
    if (delta != 0)
        addCount((long)delta, binCount);
    return val;
}
复制代码

参考文档

JDK8 ConcurrentHashMap的死锁bug
stackoverflow.com/questions/4…

Guess you like

Origin juejin.im/post/7047373540982521887