1. 问题描述:
短信平台压测过程中遇到,频繁FullGC问题, CPU使用率达到90%,fullGC基本上5s中一次, 压测结束后一直持续。
2. 原因分析:
2.1. 获取进程的堆栈日志
jstack pid > jstack.pid.log
2.2. 利用gceasy网站进行堆栈分析
官网地址: gceasy.io/
我们可以看到一共有419个线程,大部分处于wating状态, 有8个处于阻塞的状态。
2.3 堆栈分析
有8个线程基本上处于阻塞的状态,看下线程,主要mq是1mq的生产者线程和7个消费者线程处于阻塞状态,看下堆栈日志
基本上可以确认是CurrentHashmap的用法不当导致了死锁问题。
看下网站机器学习对问题原因的分析
2.4 根本原因分析
看下出现问题的代码, 上面的意思是,在计算过程中,其他线程尝试对Map执行的某些更新操作可能会被阻止,因此计算应简短而简单,并且不得尝试更新此映射的任何其他映射。
3. ConcurrentHashMap 源码分析
3.1 问题复现
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 值一样导致的死锁。
ConcurrentHashMap#computeIfAbsent 源码
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
if (key == null || mappingFunction == null)
throw new NullPointerException();
int h = spread(key.hashCode());
V val = null;
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 = mappingFunction.apply(key)) != null)
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 {
boolean added = false;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek; V ev;
if (e.hash == h &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
val = e.val;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
// debug 到这一步线程基本上夯住在这边了
if ((val = mappingFunction.apply(key)) != null) {
added = true;
pred.next = new Node<K,V>(h, key, val, null);
}
break;
}
}
}
else if (f instanceof TreeBin) {
binCount = 2;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(h, key, null)) != null)
val = p.val;
else if ((val = mappingFunction.apply(key)) != null) {
added = true;
t.putTreeVal(h, key, val);
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (!added)
return val;
break;
}
}
}
if (val != null)
addCount(1L, binCount);
return val;
}