本例使用重写后的代码,来复现用户的情况,主要涉及ATP堆分析中的支配关系视图、我引用谁以及谁引用我等场景。
异常现象
有用户反馈他们应用有一个cache,除非应用退出,正常情况下对象不会被回收,通过堆分析发现对象也没有GC根路径。
根据用户提供的信息,我们可以在支配关系视图中通过名字找到问题对象,所谓支配关系视图是指:
表示对象的支配关系的图。假如对象A支配B,就表示垃圾回收器必须先释放B对象,然后才能释放A对象。以此关系构造的图就叫做对象支配图,该视图是堆分析中最重要的视图,它展示了堆中占比最大的是何种对象,以及该对象为何仍然存活(可以通过GC根路径查看)。
发现Rules对象确实不存在垃圾回收根路径(GC Root Path),理论上不存在GC根路径对象就会被回收,至于为什么没有被回收需要进一步分析。
堆分析
1. 使用我引用谁分析对象布局
选择上一步找到的问题对象,点击我引用谁,该功能可以显示当前对象引用了哪些对象:
点击value,查看谁引用我,该功能可以显示哪些对象引用了该对象:
可以看到此时value除了被Rules引用之外,还被referent引用,这个referent是WeakHashMap的键,根据分析我们复原代码可能如下:
class Rules { byte[] value; }
public class Test{
static WeakHashMap<byte[], ??Value??> cache;
}
然后我们继续查看WeakHashMap的键值对构造。进入支配关系视图,选择任意WeakHashMap$Entry,点击我引用谁:
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;
}