The ArrayList
class uses a simple strategy for managing space. When the list's backing array is full and you add another element, the ArrayList
allocates a larger array and copies the elements from the old one to the new one. The new array's size will be 50% larger than the old one, up to the architectural limit on an array's size. The following is the Java 8 implementation:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
In your example, you are repeatedly adding elements to the list, which is causing the backing array for the list to grow, and grow, and grow.
Next, a Reference
is class with 4 fields (in Java 8), one of which is the referenent
... which points to the object that is the target of the Reference
. When a Reference
is broken, what happens is that the referent
is set to null
, thereby allowing the target object to be GC'd (typically in the next GC cycle ... but not necessarily). But the Reference
object itself is NOT freed ... unless you make it unreachable.
In your example, you are not doing anything to make the WeakReference
objects unreachable. Instead, they are building up in the list.
Eventually, the memory consumed by the list and the (broken) WeakReference
objects that it holds will fill the heap, and you get an OOME. (It will probably happen in a Arrays.copyOf
call ... but could also happen when you are allocating a WeakReference
or a Calendar
)
If you want your list to actually release space in response to memory pressure, then you will need to detect and remove the broken WeakReference
objects from the list. This is not trivial.
You could enqueue each WeakReference
with in a ReferenceQueue
, and have the queue processor remove each WeakReference
as it is broken. However, this would be inefficient since list removal is O(N).
Another idea would be to have the queue processor increment a private counter, and have the add
method use the counter to decide when it should scan and remove broken references.
You should also consider calling ArrayList::trimToSize
... or equivalent ... to reduce the backing store size. But bear in mind that that temporarily increases memory utilization.
This probably requires a custom List
implementation.