监控Java对象回收的原理与实现
一.监控Java对象回收的目的
监控Java对象是否回收的目的是:为了实现内存泄露报警。
内存泄露是指程序中对象生命周期(点击查看详情)已经进入不可见阶段,但由于编码错误或系统原因,仍然存在着GC roots持有或间接持有该对象的引用,导致该对象的生命周期无法继续向下流转,也就无法释放的现象。简单的来说即是:已实例化的对象长期被持有且无法释放或不能按照对象正常的生命周期进行释放。(点击这里查看《[Android]内存泄露排查实战手记》)
实现内存泄露报警,可以发现并解决程序上编码的错误,降低应用的内存使用,减少应用OOM的机率。
在本人Android开发中,监控的对象为Activity。
二.监控Java对象回收的原理
下图1中。对象X在失去了所有的强引用后(普通Java对象为在失去了所有的强引用,在Android中如Activity执行了onDestroy()方法),往listGCLog中添加该对象X的特征日志,然后listGCLog进入黄色的等待时间区域,如果在该等待时间内,对象X正常被终结,则从listGCLog中删除该对象的特征日志;如果在等待时间内仍然未被终结,则时间一过,程序检查listGCLog是否为空,并在不为空时做出内存泄露的报警。
图1. 对象的监控示意图
三.监控Java对象回收的时机
如果判定Java对象已经被回收呢?可以有3种办法:
1. Java对象执行了finalize()方法
这个方法的实现依据每个对象在被垃圾回收器回收之前,都会调用该对象的finalize()方法。在该finalize()方法内执行图1中从listGC中删除X特征日志的操作,即不会引起内存泄露的报警了。
但这并不是一种好的实现方式。在分配该对象时,JVM需要在垃圾回收器上注册该对象,以便在回收时能够执行该重载方法;在该方法的执行时需要消耗CPU时间且在执行完该方法后才会重新执行回收操作,即至少需要垃圾回收器对该对象执行两次GC。
见下图2。回收重写finalize()方法的对象和正常的对象相比,前者所花费的回收时间比后者多了好多倍。当测试数量是10000时,前者消耗433ms是后者95ms的将近5倍;当数量越多时,时间差距则越来越大;当测试数量达到50000个时,前者消耗7553ms已经是后者217ms的35倍了!!
图2. 对象回收的时间消耗对比图
2. 利用WeakReferences(弱引用),当WeakReferences.get()返回为null时,即表示该弱引用的对象已经或处于垃圾回收器回收阶段。
与强引用和软引用相比,弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
当弱引用的对象已经或处于垃圾回收器回收阶段时,通过get()方法返回的值为null,此时执行图1中从listGC中删除X特征日志的操作,即不会引起内存泄露的报警了。
3. 利用PhantomReferences(虚引用)和ReferenceQueue(引用队列),当PhantomReferences被加