Java heap analysis: Uncollected garbage objects

更新时间:
复制 MD 格式

It demonstrates how to use ATP heap analysis features like the dominator tree, outgoing references, and incoming references.

Problem description

A customer reported that their application uses a cache where objects are not collected by the garbage collector unless the application exits. However, a heap analysis shows that a target object has no path to GC Roots.

Based on this information, we can locate the object by its name in the dominator tree view.

This view shows a graph of dominator relationships between objects. An object A dominates an object B if every path of references from a garbage collection root to object B must pass through object A. A graph built on this relationship is called a dominator tree. This view is the most important one for heap analysis. It shows which objects use the most memory and why they are still alive. You can view the path to the garbage collection (GC) root to understand why an object is retained.

Our analysis confirms that the Rules object has no path to GC Roots. Theoretically, an object without a path to GC Roots is eligible for garbage collection. We need to investigate why this object was not collected.

Heap analysis

1. Analyze object layout with outgoing references

Select the object and click outgoing references. This feature lists the objects referenced by the current object.

Click the value field to view incoming references. This feature shows the objects that reference it.

The value is referenced by Rules and also by referent. This referent is a key in the WeakHashMap. This analysis suggests the following reconstructed code:

class Rules { byte[] value; }
public class Test{
  static WeakHashMap<byte[], ??Value??> cache;
}

Next, let's examine the key-value structure of the WeakHashMap. Go to the dominator tree view, select any WeakHashMap$Entry object, and click outgoing references.

2. Reconstruct the object layout

The analysis reveals that the key of the WeakHashMap is a byte[] array and its value is a SoftReference. This SoftReference points to an anonymous Test$1 class instance, which contains a Rules object. The array Rules.value at address 0x7c2c00000 and the WeakHashMap key array at address 0x7c2c00000 are the same object, as they share the same memory address:

class Rules { byte[] value; }
class Test$1{
  ...
	Rules val$r;
}
public class Test{
  static WeakHashMap<byte[], SoftReference<Test$1>> cache;
}

Conclusion

The byte[] key of the WeakHashMap is part of the Test$1 object, which is referenced by the map's value (a SoftReference). Although the Test$1 object itself is not strongly referenced from anywhere else, it is held by a SoftReference. The garbage collector gives special treatment to soft reference objects, clearing them only when memory is in high demand.

Soft reference objects are cleared at the discretion of the garbage collector in response to memory demand.

This behavior explains why the Rules object, despite having no path to GC Roots, was not collected. It is wrapped inside the Test$1 object, which is held by a SoftReference and therefore not immediately eligible for collection.

To resolve this issue, simply replace the SoftReference with a WeakReference. The garbage collector clears a WeakReference during each collection cycle once its referent becomes eligible for collection. The corrected implementation is as follows:

class Rules { byte[] value; }
class Test$1{
  ...
	Rules val$r;
}
public class Test{
  static WeakHashMap<byte[], WeakReference<Test$1>> cache;
}