-2

My understanding is that a weakly reachable object is garbage collected at the next GC cycle, if that is true then I should never get OutOfMemoryError for below code, but I get OOM error after running for 10-15 minutes.

    List<Object> list = new ArrayList<>();
    while (true) {
        for(int i = 0; i < 1000000; i++){
            list.add(new WeakReference<Object>(Calendar.getInstance()));
        }
    }

Can someone please explain why I still get OOM, I understand that size of the list will keep on increasing but my point here is that Calender objects I am creating using Calendar.getInstance() will be GC'ed after every 1000000 iteration so if I am not getting OOM error in 1000000 iteration then I should not get it for any subsequent loops because all previous Calender objects would have GC'ed in next loop?

pjj
  • 2,015
  • 1
  • 17
  • 42

1 Answers1

3

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.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216