All objects implementing finalize() methods are garbage-collected via a special queue. In this queue, the JVM stores the Finalizer objects and runs the finalize method sequentially in a daemon thread with lower priority than typical threads. When some objects implement long-running finalize methods, the finalizing thread may not be able to keep up with application creating new finalizable objects. This can lead to heap exhaustion.
Here’s an example of OOM heap dump:
Due to a very process-intensive finalize() method in a frequently used object (not shown in the image), you see many Finalizer objects queued up inside the heap. If you look at Lucene’s SegmentReader objects, they are in “Pending Finalization” state. So the application isn’t holding on to a large number of these objects but the finalization queue is so backed up that these objects cannot be released.
In this case, the finalize() method was tidying up resources by committing database transactions, looking up and deleting cached objects/file system folders no longer necessary, and such. As widely known, using finalizers leads to expensive overhead on GC (not to mention not dependable). So these kind of tasks should be triggered explicitly from your code instead.
Other possible mitigation include:
- In the case of batch process, you could introduce pause based on time or some other factors, such as pending finalization count and heap usage. You can get the approximate number of objects for which finalization is pending via MemoryMXBean.getObjectPendingFinalizationCount() method. This MBean also has getHeapMemoryUsage() method. Another API to look into is Runtime. It has runFinalization() method that can explicitly run the finalization methods of any objects pending finalization. This might be good to call right before pausing.
- Controlling the processing load is another option for batch process. You can either process in smaller batches or use thread pool mechanism.
- Use UseCompressedOops option and bigger heap size.
- Not sure about this one but you could experiment with CMS GC collection mechanism to see if that can trigger finalize methods quicker with less wait around time in the beginning if it’s not currently being used.