今天在社区看到一个有意思的JDK Bug,链接地址https://club.perfma.com/article/2041676
问题是这样的,ConcurrentLinkedQueue在JDK7中remove方法会发生内存溢出,写代码去验证我这里就省略了
JDK7
如下List-1是JDK7中的源码
List-1
public boolean remove(Object o) {
if (o == null) return false;
Node<E> pred = null;
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null &&
o.equals(item) &&
p.casItem(item, null)) {
Node<E> next = succ(p);
if (pred != null && next != null)//1
pred.casNext(p, next);//2
return true;
}
pred = p;
}
return false;
}
来分析下是如何产生内存溢出的,如下图1,首先add(1),之后add(2),此时2的next是null,用List-1中的remove()删除2后,如下图2,该节点的value只是设置为null,但是并没有将该节点从链表中删除,这是因为List-1中1处,由于链表中最后一个节点的next总是null,所以无法执行3处的代码,即释放最后一个节点
图1
图2
此时add(3)后,变为如下图3,之后再将3删除后,是如下图4,节点3还是未移除,还是和2节点未删除的原因一致,所以可以看出这就是内存溢出的原因
图3
图4
JDK8
JDK8中的remove如下List-2所示
List-2
public boolean remove(Object o) {
if (o != null) {
Node<E> next, pred = null;
for (Node<E> p = first(); p != null; pred = p, p = next) {
boolean removed = false;
E item = p.item;
if (item != null) {//1
if (!o.equals(item)) {
next = succ(p);
continue;
}
removed = p.casItem(item, null);
}
next = succ(p);//2
if (pred != null && next != null) // unlink 3
pred.casNext(p, next);
if (removed)
return true;
}
}
return false;
}
JDK8中add(1)和add(2)后,还是如图1所示,之后用List-2中的remove删除2后,还是如图2所示,即2节点未被移除链表,不要慌,看接下来
之后add(3)后,还是如图3,此时用List-2的remove删除3后,实际上3节点未被移除队列,而是2节点被移除了队列,如下图5
图5
类分析下,在List-2的1处,由于2节点值是null,所以不会执行if中的语句,在2处将next指向3节点,之后再3处由于pred(节点1)不为null且next(节点3)不为null,所以将pred的next设置为节点3,这样节点2的next虽然连着节点3,但是由于没有对象引用节点2,所以节点2会被GC回收。