FULL PRODUCT VERSION :
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
A DESCRIPTION OF THE PROBLEM :
ConcurrentLinkedQueue leaks memory when removing the last element of a non-empty queue.
This has been reported for the Cassandra project (see https://issues.apache.org/jira/browse/CASSANDRA-9549, the last comment on 15-Jul-2015), and in the Jetty project (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=477817)
The problem is in method remove(Object), when the object last in the queue is the one to remove.
What happens is that casItem() will succeed, but next = succ(p) will be null (since p is the last node), so the next "if" statement will not be entered and the last Node not unlinked.
ADDITIONAL REGRESSION INFORMATION:
AFAIK, this bug is present in the latest JDK 7, 8 and 9 releases.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case below.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No memory leaks.
ACTUAL -
Memory is leaked and when running with very small heaps (say 1 MiB), it takes about 50k iterations to exhaust the heap.
Note also that because the last Node instance is never removed, more Nodes are appended and each further add/remove operation will have to navigate the linked list until the end with much CPU consumption and slowdown of performance of the methods.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
public class CLQBug
{
public static void main(String[] args)
{
Queue<Object> queue = new ConcurrentLinkedQueue<>();
queue.offer(new Object());
Object item = new Object();
long iterations = 0;
try
{
while (true)
{
++iterations;
queue.offer(item);
queue.remove(item);
}
}
catch (OutOfMemoryError e)
{
queue = null;
System.err.println("iterations: " + iterations);
throw e;
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
No workaround, if not making sure that the object you are removing is not the last.