Java堆分析 - 垃圾对象不回收

本例使用重写后的代码,来复现用户的情况,主要涉及ATP堆分析中的支配关系视图、我引用谁以及谁引用我等场景。

异常现象

有用户反馈他们应用有一个cache,除非应用退出,正常情况下对象不会被回收,通过堆分析发现对象也没有GC根路径。

根据用户提供的信息,我们可以在支配关系视图中通过名字找到问题对象,所谓支配关系视图是指:

表示对象的支配关系的图。假如对象A支配B,就表示垃圾回收器必须先释放B对象,然后才能释放A对象。以此关系构造的图就叫做对象支配图,该视图是堆分析中最重要的视图,它展示了堆中占比最大的是何种对象,以及该对象为何仍然存活(可以通过GC根路径查看)。

image

发现Rules对象确实不存在垃圾回收根路径(GC Root Path)理论上不存在GC根路径对象就会被回收,至于为什么没有被回收需要进一步分析。

堆分析

1. 使用我引用谁分析对象布局

选择上一步找到的问题对象,点击我引用谁,该功能可以显示当前对象引用了哪些对象:

image

点击value,查看谁引用我,该功能可以显示哪些对象引用了该对象:

image

可以看到此时value除了被Rules引用之外,还被referent引用,这个referent是WeakHashMap的键,根据分析我们复原代码可能如下:

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

然后我们继续查看WeakHashMap的键值对构造。进入支配关系视图,选择任意WeakHashMap$Entry,点击我引用谁

imageimage

2. 还原对象布局

从上图中我们会发现,WeakHashMap的键是byte[]数组,值是SoftReference,其中SoftReference引用了一个Test$1的匿名数据结构,该数据结构有一个Rules对象,Rules.val这个数组0x7c2c00000和WeakHashMap键的数组0x7c2c00000地址一致,说明它们是一个对象:

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

分析结论

到这里我们基本可以得出结论了,WeakHashMap的键byte[]实际上是值Test$1里面的一部分,虽然Test$1对象本身不再被其他对象引用,但是它被SoftReference引用,而SoftReference会被GC特别处理,仅当垃圾回收器需要内存的时候才会回收被SoftReference引用的对象:

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

所以这就导致了前面出现的情况:Rules对象已经没有了GC根路径,但是因为包裹它的Test$1对象是SoftReference,它没有被回收。

为了解决这个问题,我们可以简单地使用WeakReference引用Test$1,WeakReference会在每次GC时回收被它引用的对象,最终结果如下:

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